Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java 8 stream - merge collections of objects sharing the same Name and who follow each other

Suppose a class Person:

public class Person {
    private String name;
    private int amount;
}

and suppose a collection of Person:

persons = [{"Joe", 5}, {"Joe", 8}, {"Joe", 10}, {"Jack", 3}, {"Jack",6}, {"Joe" 4}, 
           {"Joe", 7}, {"Jack", 12}, {"Jack", 15}, {"Luke", 10}, {"Luke", 12}]

What i want is to get list of Person with merged element who have same name and who follow each other and sum the amount (with java 8 Stream); a list like this:

Perons = [{"Joe", 23}, {"Jack", 9}, {"Joe", 11}, {"Jack", 27}, {"Luke", 22}]
like image 759
T. Mehdi Avatar asked Jan 30 '26 03:01

T. Mehdi


2 Answers

You should create your own custom Collector for this.

class AdjacentNames {

    List<Person> result = new ArrayList<>();
    Person last = null;

    void accumulate(Person person) {
        if (last != null && last.getName().equals(person.getName())) {
            last.setAmount(last.getAmount() + person.getAmount())
        } else {
            last = new Person(person.getName(), person.getAmount());  // Clone
            result.add(last);
        }
    }

    AdjacentNames merge(AdjacentNames other) {
        List<Person> other_list = other.finisher();

        if (other_list.size() > 0) {
            accumulate(other_list.remove(0));
            result.addAll(other_list);
            last = result.get(result.size()-1);
        }

        return this;
    }

    List<Person> finisher() {
        return result;
    }

    public static Collector<Person, AdjacentNames, List<Person>> collector() {
        return Collector.of(AdjacentNames::new,
                            AdjacentNames::accumulate,
                            AdjacentNames::merge,
                            AdjacentNames::finisher);
    }
}

And use like:

List<Person> result = persons.stream().collect(AdjacentNames.collector());
like image 129
AJNeufeld Avatar answered Feb 01 '26 15:02

AJNeufeld


The solution you are looking for is bit complicated with stream approach, I would suggest to go with basic for loop and use Map with a duplicate logic for each sequence

List<Person> persons = List.of(new Person("Jeo", 5), new Person("Jeo", 5), new Person("Jack", 8),
            new Person("Luke", 5), new Person("Jeo", 5), new Person("Jeo", 5));

    List<Person> result = new ArrayList<>();
    Map<String, Integer> check = new HashMap<String, Integer>();
    for (Person per : persons) {

        if (check.containsKey(per.getName())) {
            check.compute(per.getName(), (k, v) -> v + per.getAmount());

        } else if (check.size() == 0) {
            check.put(per.getName(), per.getAmount());

        }else {
            result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());
            check.clear();
            check.put(per.getName(), per.getAmount());
        }
    }
    result.add(check.entrySet().stream().map(e->new Person(e.getKey(), e.getValue())).findFirst().get());

I would also suggest to go with stream approach to just get each Person sum based on name by using Collectors.groupingBy and summingInt and save them to LinkedHashMap by maintaining the order, and then convert each entry back to Person object.

person.stream()
       .collect(Collectors.groupingBy(Person::getName, LinkedHashMap::new,Collectors.summingInt(Person::getAmount)))
       .entrySet()
       .stream()
       .map(entry->new Person(entry.getKey(),entry.getValue()))
       .collect(Collectors.toList());
like image 36
Deadpool Avatar answered Feb 01 '26 15:02

Deadpool



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!