Suppose I have the list below I would like to return a result that has only one Person with the name "Sam"
- "Fred"
but with 25
amount
public class Java8Test{
private static class Person {
private String name;
private String lastName;
private int amount;
public Person(String name, String lastName, int amount) {
this.name = name;
this.lastName = lastName;
this.amount = amount;
}
}
public static void main(String[] args) {
List<Person> people = new ArrayList<>();
people.add(new Person("Sam","Fred",10));
people.add(new Person("Sam","Fred",15));
people.add(new Person("Jack","Eddie",10));
// WHAT TO DO HERE ?
}
}
NOTE:
The example above is only for clarification, what I am looking for is a general map/reduce like functionality with Java 8.
You could iterate your people
list and use a map to merge people with the same name - lastName
pair:
Map<String, Person> map = new HashMap<>();
people.forEach(p -> map.merge(
p.getName() + " - " + p.getLastName(), // name - lastName
new Person(p.getName(), p.getLastName, p.getAmount()), // copy the person
(o, n) -> o.setAmount(o.getAmount() + n.getAmount()))); // o=old, n=new
Now map.values()
is a reduced Collection<Person>
as per your requirements.
If you have the possibility to add a copy-constructor and a couple of methods to the Person
class:
public Person(Person another) {
this.name = another.name;
this.lastName = another.lastName;
this.amount = another.amount;
}
public String getFullName() {
return this.name + " - " + this.lastName;
}
public Person merge(Person another) {
this.amount += another.amount;
}
Then, you could simplify the first version of the code, as follows:
Map<String, Person> map = new HashMap<>();
people.forEach(p -> map.merge(p.getFullName(), new Person(p), Person::merge));
This utilizes the Map.merge
method, which is very useful for this cases.
You can use groupingBy
, reducing
and others stuff to :
- group Person by same name and lastName
- sum the value of their amount
- create a person with these attributs
people = people.stream().collect(
Collectors.groupingBy(o -> Arrays.asList(o.name, o.lastName),
Collectors.summingInt(p->p.amount))
.entrySet()
.stream()
.map(Person::apply).collect(Collectors.toList());
people.forEach(System.out::println);
//Prints :
Sam Fred 25
Jack Eddie 10
And these two are same (my IDE suggest it to me, I assume that i don't really know how it works, if someones knows : explain it to us in comment)
.map(Person::apply)
.map(e -> new Person(e.getKey().get(0), e.getKey().get(1), e.getValue())
I can think of this sort of hacky way to do it:
TreeSet<Person> res = people.stream()
.collect(Collector.of(
() -> new TreeSet<>(Comparator.comparing(Person::getName)
.thenComparing(Person::getLastName)),
(set, elem) -> {
if (!set.contains(elem)) {
set.add(elem);
} else {
Person p = set.ceiling(elem);
p.setAmount(elem.getAmount() + p.getAmount());
}
},
(left, right) -> {
throw new IllegalArgumentException("not for parallel");
}));
That is without changing the definition of the Person
at all. It's a Set
that's returned (according to firstname
and lastname
), but that is what you want here anyway.
You can use the stream groupingBy
to group by multiple columns:
Function<Person, List<Object>> key = p -> Arrays.asList(p.name, p.lastName);
final Map<List<Object>, Integer> collect = people.stream()
.collect(Collectors.groupingBy(key, Collectors.summingInt(p -> p.amount)));
System.out.println(collect);
results in
{[Sam, Fred]=25, [Jack, Eddie]=10}
From there, you can create new Person instances from the map values.
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