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}]
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());
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());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With