We have two lists in Java, as shown below. And I need to get all elements from these two lists and if there is same id
with the same date we need to sum the
cost` of such elements in these lists.
List<A> listA = new ArrayList<>();
List<A> listB = new ArrayList<>();
List<A> results = new ArrayList<>();
listA
:
Id | Date | Cost |
---|---|---|
1 | 2022-01-01 | 11.65 |
2 | 2022-02-01 | 12.65 |
2 | 2022-03-01 | 13.65 |
3 | 2022-05-01 | 19.5 |
listB
:
Id | Date | Cost |
---|---|---|
1 | 2022-04-01 | 1.65 |
1 | 2022-05-01 | 134.65 |
2 | 2022-02-01 | 12.65 |
2 | 2022-09-01 | 7.8 |
3 | 2022-06-01 | 3.65 |
The results should be
results list should be - >
Id | Date | Cost |
---|---|---|
1 | 2022-01-01 | 11.65 |
1 | 2022-04-01 | 1.65 |
1 | 2022-05-01 | 134.65 |
2 | 2022-02-01 | 25.3* |
2 | 2022-03-01 | 13.65 |
2 | 2022-09-01 | 7.8 |
3 | 2022-05-01 | 19.5 |
3 | 2022-06-01 | 3.65 |
* (listA.cost listB.cost this is based on date condition and id)
What I have tried till now is this
Stream.concat(
listA.stream().map(d -> new Result(d.getId(), d.getDate()), d.getCost()),
listB.stream().map(b -> new Result(b.getId(), b.getDate(), b.getCost())
)
.collect(Collectors.toList());
I am able to get all the data but after this step I need to get all the data and if there is same date
and same id
we need to sum up the cost
from listA
with the cost of listB
CodePudding user response:
get all the data and if there is same
date
and sameid
we need to sum up the cost fromlistA
with the cost oflistB
It can be achieved by grouping the data from the these list into an intermediate map. The key of this map should be an object that would be capable to incorporate date
and id
. There are several quick and dirty approaches like concatenate them as strings, but the correct way is to define a record (or a class). And the values of the intermediate map would represent a sum of costs that are mapped to the same combination of date
and id
.
Then we can create a stream over the entries of the auxiliary map, turn each entry into an object A
and collect into a list.
That's how it can be implemented.
Define a record, IdDate
, to use as a key:
public record IdDate(long id, LocalDate date) {}
List<A> listA =
Arrays.asList(new A(1, LocalDate.parse("2022-01-01"), 11.65), // for Java 9 use - List.of()
new A(2, LocalDate.parse("2022-02-01"), 12.65),
new A(2, LocalDate.parse("2022-03-01"), 13.65),
new A(3, LocalDate.parse("2022-05-01"), 19.5));
List<A> listB =
Arrays.asList(new A(1, LocalDate.parse("2022-04-01"), 1.65), // for Java 9 use - List.of()
new A(1, LocalDate.parse("2022-05-01"), 134.65),
new A(2, LocalDate.parse("2022-02-01"), 12.65),
new A(2, LocalDate.parse("2022-09-01"), 7.8),
new A(3, LocalDate.parse("2022-06-01"), 3.65));
List<A> results =
Stream
.concat(listA.stream(), listB.stream())
.collect(
Collectors.groupingBy( // creating an intermediate map `Map<IdDate, Double>`
a -> new IdDate(a.getId(), a.getDate()), // classifier fuction - generating a key
Collectors.summingDouble(A::getCost) // downstream collector - combining values mapped to the same key
)
)
.entrySet()
.stream()
.map(entry -> new A(entry.getKey().id(), // transforming an entry into an object `A`
entry.getKey().date(),
entry.getValue()))
.toList(); // In earlier Java: .collect(Collectors.toList());
results.forEach(System.out::println);
Output:
A{id=2, date=2022-09-01, cost=7.8}
A{id=2, date=2022-03-01, cost=13.65}
A{id=2, date=2022-02-01, cost=25.3}
A{id=3, date=2022-06-01, cost=3.65}
A{id=3, date=2022-05-01, cost=19.5}
A{id=1, date=2022-05-01, cost=134.65}
A{id=1, date=2022-04-01, cost=1.65}
A{id=1, date=2022-01-01, cost=11.65}
CodePudding user response:
This method will work:
Our A
class:
class A implements Comparable<A>{
private int id;
private String date;
private double cost;
public A(int i, String d, double c){
id = i;
date = d;
cost = c;
}
public int getID(){
return id;
}
public String getDate(){
return date;
}
public double getCost(){
return cost;
}
public void setCost(double c){
cost = c;
}
@Override public int compareTo(A compareID) {
int comp = (compareID).getID();
return this.id - comp;
}
public String toString(){
return id " " date " " cost;
}
}
Main class:
import java.util.*;
class Main {
public static void main(String[] args) {
List<A> listA = new ArrayList<>();
List<A> listB = new ArrayList<>();
List<A> results = new ArrayList<>();
listA.add(new A(1, "2022-01-01", 11.65));
listA.add(new A(2, "2022-02-01", 12.65));
listA.add(new A(2, "2022-03-01", 13.65));
listA.add(new A(3, "2022-05-01", 19.5));
listB.add(new A(1, "2022-04-01", 1.65));
listB.add(new A(1, "2022-05-01", 134.65));
listB.add(new A(2, "2022-02-01", 12.65));
listB.add(new A(2, "2022-09-01", 7.8));
listB.add(new A(3, "2022-06-01", 3.65));
results = listA;
for(int i = 0; i < results.size(); i ){
for(int j = 0; j < listB.size(); j ){
if((results.get(i)).getDate().equals((listB.get(j)).getDate()) && (results.get(i)).getID() == (listB.get(j)).getID()){
results.get(i).setCost(results.get(i).getCost() listB.get(j).getCost());
listB.remove(j);
j -= 1;
}
}
}
results.addAll(listB);
Collections.sort(results);
System.out.println(results);
}
}
Output:
[1 2022-01-01 11.65, 1 2022-04-01 1.65, 1 2022-05-01 134.65, 2 2022-02-01 25.3, 2 2022-03-01 13.65, 2 2022-09-01 7.8, 3 2022-05-01 19.5, 3 2022-06-01 3.65]
In our A
class, we define our private instance variables, being id
, date
, cost
, and set up constructors, accessors, and mutators for the class. We will also set up our own comparator so we can sort by ID.
In our main class, we set results
equal to listA
. Next, we use a nested for loop to iterate through both results
and listB
. If the i
th element in results
and the j
th element in listB
have the same id
and date
, we will add the values in listA
, and delete that element in listB
. Finally, if there are any elements left over in listB
, we add them to results
, and then sort the list.
This is not the most efficient approach as it involves a nested for loop and sorting, but this will work nonetheless.
I hope this answered your question! Please let me know if you have any further questions or clarifications :)
CodePudding user response:
Well, you can use groupingBy
in combination with reduce
to get what you want.
First, we're creating a groupingBy key (in your case, thats a class with id
and date
). We may as well create a record.
record GroupByKey(int id, String date) {
public static GroupByKey fromResult(Result result) {
return new GroupByKey(result.id(), result.date());
}
}
Note that I added a method named fromResult
, which is able to convert a Result
instance to its corresponding groupingBy key.
Then the following will do:
var result = Stream.of(a, b)
.flatMap(List::stream)
.collect(groupingBy(
GroupByKey::fromResult,
reducing((l, r) -> new Result(l.id(), l.date(), l.cost() r.cost()))
));
What happens here, is the following. flatMap
makes sure that each element of both lists are the subject of a new stream. Then groupingBy
creates a Map
with as key the GroupingByKey
and as value a list with all corresponding Result
instances. Now the only thing what needs to be done here, is take all elements from the list within the map, and create a single Result
instance with all costs added to eachother.