Stream groupingBy: reducing to first element of list

25,243

Solution 1

Actually, you need to use Collectors.toMap here instead of Collectors.groupingBy:

Map<String, Valuta> map = 
    getValute().stream()
               .collect(Collectors.toMap(Valuta::getCodice, Function.identity()));

groupingBy is used to group elements of a Stream based on a grouping function. 2 Stream elements that will have the same result with the grouping function will be collected into a List by default.

toMap will collect the elements into a Map where the key is the result of applying a given key mapper and the value is the result of applying a value mapper. Note that toMap, by default, will throw an exception if a duplicate is encountered.

Solution 2

It's a bit late in the game, but try this:

Map<String, Valuta> map = 
    getValute().stream()
               .collect(Collectors.groupingBy(Valuta::getCodice,
                            Collectors.collectingAndThen(
                                Collectors.toList(), 
                                values -> values.get(0))));

Solution 3

You could use Collectors.toMap(keyMappingFunction, valueMappingFunction)

Map<String, Valuta> map = list
        .stream()
        .collect(Collectors.toMap(Valuta::getCodice, v -> v));

You can replace v->v with Function.identity() if you find it more readable. Note that toMap, by default, will throw an exception if a duplicate is encountered.

Solution 4

The toMap version which opts to choose the 1st value on collisions instead of throwing an exception is:

Collectors.toMap(keyMapper, valueMapper, mergeFunction) ex:

Map<String, Valuta> map = list
        .stream()
        .collect(Collectors.toMap(Valuta::getCodice, v -> v, (v1, v2) -> v1));

Solution 5

Not completely related to this question but quiet similar case .If you want to group list by multiple params and after that need return object of different type as value then you can try this solution . Hope helps to someone!

public Map<Integer, Map<String, ObjectTypeB>> setObjectTypeB(List<ObjectTypeA> typeAList) {

   Map<Integer, Map<String, ObjectTypeB>> map = typeAList.stream()
        .collect(groupingBy(ObjectTypeA::getId,
         groupingBy(ObjectTypeA::getDate, 
         collectingAndThen(mapping(this::getObjectTypeB,toList()),values -> values.get(0)))));    
    return map;
}

    public ObjectTypeB getObjectTypeB(ObjectTypeA typeA) {
       ObjectTypeB typeB = new ObjectTypeB();
       typeB.setUnits(typeA.getUnits());
       return typeB;
}
Share:
25,243
Fabio B.
Author by

Fabio B.

http://www.linkedin.com/in/fabiobozzo

Updated on July 09, 2022

Comments

  • Fabio B.
    Fabio B. almost 2 years

    I have a List<Valuta> which can be represented (simplified) JSON-style:

    [ { codice=EUR, description=Euro, ratio=1 }, { codice=USD, description=Dollars, ratio=1.1 } ]

    I want to transform that in a Map<String, Valuta> like this:

    { EUR={ codice=EUR, description=Euro, ratio=1 }, USD={ codice=USD, description=Dollars, ratio=1.1 }}

    I wrote this one-liner:

    getValute().stream().collect(Collectors.groupingBy(Valuta::getCodice));
    

    but this returns a Map<String, List<Valuta>> instead of what I need.

    I suppose mapping() function would work for me, but don't know how.

  • user909481
    user909481 almost 7 years
    How to do the same with groupingBy? I cannot use toMap because I must expect duplicate keys from the key mapper. I see groupingBy has signature for downstream collector as 2nd argument but couldn't make it work yet.
  • BaiJiFeiLong
    BaiJiFeiLong about 6 years
    This is what i am finding for
  • fricke
    fricke over 5 years
    this is what's needed in case you actually do have duplicates, but don't want to use it
  • user1735921
    user1735921 over 4 years
    Lets say I want Map<String, String> instead of Map<String, Valuta>, I need Valuta.getDescription() , basically, is that possible?
  • jocull
    jocull over 4 years
    Be aware that Collectors.toMap will throw an exception if you somehow have duplicate keys! They are not completely equivalent.
  • jocull
    jocull over 4 years
    You might be able to use the mergeFunction of .toMap to simply drop any duplicates and avoid extra object allocations: stackoverflow.com/a/32313069/97964
  • jocull
    jocull over 4 years
    Discovered this - you might be able to use the mergeFunction of .toMap to simply drop or replace any duplicates and avoid extra object allocations: stackoverflow.com/a/32313069/97964
  • toongeorges
    toongeorges about 4 years
    Why create a list in the stream? Here your solution adapted without creating the list: getValute().stream().collect(groupingBy(Valuta::getCodice, reducing(null, identity(), (first, last) -> last)))
  • rogerdpack
    rogerdpack about 3 years
    These appear to be three equivalent ways to perform the default groupingBy behavior. Which is interesting but I don't think what the OP wanted (which was a single k -> v). :)
  • andresp
    andresp over 2 years
    Does the merge function preserve ordering? i.e. is it guaranteed that v1 always appears before v2 in list?
  • 3ygun
    3ygun over 2 years
    @andresp If you're working with stream() called on an ArrayList (which uses it's ordered iterator) than yes. Otherwise, it's dependent on your collection see something like stackoverflow.com/a/40583642/6480404