Iterate over Collected list in Java 8 GroupingBy - java-8

I have a List of Objects say List<Type1> that I have grouped using type.(using groupingBy)
Now I want to convert that Map> into Type2 that has both the list and the Id of that group.
class Type1{
int id;
int type;
String name;
}
class Type2{
int type;
List<Type1> type1List;
}
This is what I have written to achieve this:
myCustomList
.stream()
.collect(groupingBy(Type1::getType))
.entrySet()
.stream()
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
.collect(Collectors.toList());
This works perfectly. But I am trying to make the code even cleaner. Is there a way to avoid streaming this thing all over again and use some kind of flatmap to achieve this.

You can pass a finisher function to the collectingAndThen to get the work done after the formation of the initial map.
List<Type2> result = myCustomList.stream()
.collect(Collectors.collectingAndThen(Collectors.groupingBy(Type1::getType),
m -> m.entrySet().stream()
.map(e -> new Type2(e.getKey(), e.getValue()))
.collect(Collectors.toList())));

You should give Type2 a constructor of the form
Type2(int type, List<Type1> type1List) {
this.type = type;
this.type1List = type1List;
}
Then, you can write .map(type1Item -> new Type2(type1Item.getKey(), type1Item.getValue())) instead of
.map(type1Item -> new Type2() {
{
setType(type1Item.getKey());
setType1List(type1Item.getValue());
}
})
See also What is Double Brace initialization in Java?
In short, this creates a memory leak, as it creates a subclass of Type2 which captures the type1Item its entire lifetime.
But you can perform the conversion as part of the downstream collector of the groupingBy. This implies that you have to make the toList explicit, to combine it via collectingAndThen with the subsequent mapping:
Collection<Type2> collect = myCustomList
.stream()
.collect(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))))
.values();
If you really need a List, you can use
List<Type2> collect = myCustomList
.stream()
.collect(collectingAndThen(groupingBy(Type1::getType,
collectingAndThen(toList(), l -> new Type2(l.get(0).getType(), l))),
m -> new ArrayList<>(m.values())));

You can do as mentioned below:
type1.map( type1Item -> new Type2(
type1Item.getKey(), type1Item
)).collect(Collectors.toList());

Related

Efficient way to group by a given list based on a key and collect in same list java 8

I have the below class:
class A{
String property1;
String property2;
Double property3;
Double property4;
}
So the property1 and property2 is the key.
class Key{
String property1;
String property2;
}
I already have a list of A like below:
List<A> list=new ArrayList<>();
I want to group by using the key and add to another list of A in order to avoid having multiple items with same key in the list:
Function<A, Key> keyFunction= r-> Key.valueOf(r.getProperty1(), r.getProperty2());
But then while doing group by I have to take a sum of property3 and average of property4.
I need an efficient way to do it.
Note: I have skipped the methods of the given classes.
Collecting to a Map is unavoidable since you want to group things. A brute-force way to do that would be :
yourListOfA
.stream()
.collect(Collectors.groupingBy(
x -> new Key(x.getProperty1(), x.getProperty2()),
Collectors.collectingAndThen(Collectors.toList(),
list -> {
double first = list.stream().mapToDouble(A::getProperty3).sum();
// or any other default
double second = list.stream().mapToDouble(A::getProperty4).average().orElse(0D);
A a = list.get(0);
return new A(a.getProperty1(), a.getProperty2(), first, second);
})))
.values();
This could be slightly improved for example in the Collectors.collectingAndThen to only iterate the List once, for that a custom collector would be required. Not that complicated to write one...
Try like this:
Map<A,List<A>> map = aList
.stream()
.collect(Collectors
.groupingBy(item->new A(item.property1,item.property2)));
List<A> result= map.entrySet().stream()
.map(list->new A(list.getValue().get(0).property1,list.getValue().get(0).property1)
.avgProperty4(list.getValue())
.sumProperty3(list.getValue()))
.collect(Collectors.toList());
and create avgProperty4 and sumProperty3 methods like to this
public A sumProperty3(List<A> a){
this.property3 = a.stream().mapToDouble(A::getProperty3).sum();
return this;
}
public A avgProperty4(List<A> a){
this.property4 = a.stream().mapToDouble(A::getProperty4).average().getAsDouble();
return this;
}
result = aList.stream().collect(Collectors
.groupingBy(item -> new A(item.property1, item.property2),
Collectors.collectingAndThen(Collectors.toList(), list ->
new A(list.get(0).property1, list.get(0).property1)
.avgProperty4(list).sumProperty3(list))
)
);

map from string to multiple different strings and add to list with Java stream

I'm new to Java 8 and Streams .
I got a PolicyDefinition object, that got to two method : getAlias,getName which both returns a string .
Is there an elegant way to create a list with all aliases and names of policy definitions using Stream (created from collection of PolicyDefinition) in one statement ?
with two statements its not a problem :
List<String> policyNames =
policyDefinitions.stream()
.map(definition -> definition.getName())
.collect(Collectors.toList());
List<String> policyAlias =
policyDefinitions.stream()
.map(definition -> definition.getAlias())
.collect(Collectors.toList());
But Is it possible in one ?
Thanks a lot for the help
flatMap it!
List<String> policyNames = policyDefinitions.stream()
.flatMap(definition -> Stream.of(definition.getName(), definition.getAlias()))
.collect(Collectors.toList());
As mentioned in the comments - for tidyness, create a method in Definition
public Stream<String> allNames() {
return Stream.of(getName(), getAlias())
}
Then
List<String> policyNames = policyDefinitions.stream()
.flatMap(Definition::allNames)
.collect(Collectors.toList());
OP comments "I forgot to mention that getAlias might be null, what do you do than[sic]"
In that case, use Optional:
public Stream<String> allNames() {
return Stream.concat(Stream.of(getName()), Optional.ofNullable(getAlias()).stream())
}
Also you can create a Map with Alias as a Key and Name as a Value using groupingBuy operator

java 8 list grouping with value mapping function producing list

I have a following Person class
public class Person {
public String name;
public List<Brand> brands;
//Getters
}
and a List<Person> persons(possibly with same names). I need to group in a map of <String, List<Brand>> with Person's name as Keys and lists of accumulated Brands as values.
Something like this
Map<String, List<List<String>>> collect = list.stream().collect(
groupingBy(Person::getName, mapping(Person::getBrands, toList()))
);
produces undesired result and I know why. If the values could be somehow flatten during grouping? Is there a way to do it right there with Streams api?
java 9 will add the flatMapping collector specifically for this type of task:
list.stream().collect(
groupingBy(
Person::getName,
flatMapping(
p -> p.getBrands().stream(),
toList()
)
)
Guessing what is the desired result, you can achieve it with just toMap collector:
Map<String, List<String>> collect = persons.stream().collect(
toMap(
Person::getName,
Person::getBrands,
(l1, l2) -> ImmutableList.<String /*Brand*/>builder().addAll(l1).addAll(l2).build())
);
You will need to merge brands into a single List:
list.stream().collect(Collectors.toMap(
Person::getName,
Person::getBrands,
(left, right) -> {
left.addAll(right);
return left;
},
HashMap::new));
You can create a custom collector for the downstream to your groupBy:
Collector.of(LinkedList::new,
(list, person) -> list.addAll(person.brands),
(lhs, rhs) -> { lhs.addAll(rhs); return rhs; })
There is MoreCollectors provided in open source library: StreamEx
list.stream().collect(
groupingBy(Person::getName, MoreCollectors.flatMapping(p -> p.getBrands().stream()));

How to use lambda/Stream api to filter distinct elements by object Attribute/Property

I have a List of Object. Every object has a map with a key named "xyz". I want elements in the list which has unique value to that particular key.
I know we can do this easily with set/map but I'm particularly looking for lambda solution.
I thought this would work.
list.stream()
.filter(distinctByXyz(f -> f.getMap.get("xyz")))
.collect(Collectors.toList()));
I've a function to distinct them
private <T> Predicate<T> distinctByKey(Function<? super T, Object> keyExtractor){
Map<Object, Boolean> map = new ConcurrentHashMap<>();
return t -> map.putIfAbsent(keyExtractor.apply(t), Boolean.TRUE) == null;
}
The problem is the function f.getMap() inside filter isnt working. Showing compilation error (Cannot resolve method)
You seem to have a few typos in your code, this should work:
list
.stream()
.filter(distinctByKey(f -> f.getMap().get("xyz")))
.collect(Collectors.toList());
You are using distinctByXyz when it should really be distinctByKey. Then f.getMap that should probably be f.getMap() and also you are slightly off with your parenthesis.

Convert ImmutableListMultimap to Map using Collectors.toMap

I would like to convert a ImmutableListMultimap<String, Character> to Map<String, List<Character>>.
I used to do it in the non-stream way as follows
void convertMultiMaptoList(ImmutableListMultimap<String, Character> reverseImmutableMultiMap) {
Map<String, List<Character>> result = new TreeMap<>();
for( Map.Entry<String, Character> entry: reverseImmutableMultiMap.entries()) {
String key = entry.getKey();
Character t = entry.getValue();
result.computeIfAbsent(key, x-> new ArrayList<>()).add(t);
}
//reverseImmutableMultiMap.entries().stream().collect(Collectors.toMap)
}
I was wondering how to write the above same logic using java8 stream way (Collectors.toMap).
Please share your thoughts
Well there is already a asMap that you can use to make this easier:
Builder<String, Character> builder = ImmutableListMultimap.builder();
builder.put("12", 'c');
builder.put("12", 'c');
ImmutableListMultimap<String, Character> map = builder.build();
Map<String, List<Character>> map2 = map.asMap()
.entrySet()
.stream()
.collect(Collectors.toMap(Entry::getKey, e -> new ArrayList<>(e.getValue())));
If on the other hand you are OK with the return type of the asMap than it's a simple method call:
ImmutableMap<String, Collection<Character>> asMap = map.asMap();
Map<String, List<Character>> result = reverseImmutableMultiMap.entries().stream()
.collect(groupingBy(Entry::getKey, TreeMap::new, mapping(Entry::getValue, toList())));
The important detail is mapping. It will convert the collector (toList) so that it collects List<Character> instead of List<Entry<String, Character>>. According to the mapping function Entry::getValue
groupingBy will group all entries by the String key
toList will collect all values with same key to a list
Also, passing TreeMap::new as an argument to groupingBy will make sure you get this specific type of Map instead of the default HashMap

Resources