Home > other >  Merge all elements from Two Lists having the same Date and Id and sum up their Costs
Merge all elements from Two Lists having the same Date and Id and sum up their Costs

Time:07-02

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 same id we need to sum up the cost from listA with the cost of listB

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}

A link to Online Demo

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 ith element in results and the jth 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.

  • Related