Lost In time
Lost In time

Reputation: 51

Transforming into Map using Java 8 and lambdas

I have

List<Gift> gifts = new ArrayList<>();
gifts .add(new Gift().withType(INF, CHD));
gifts .add(new Gift().withType(ADT, CHD));
gifts .add(new Gift().withType(INF, ADT));

Gift has a method List<Type> getTypes();

and now I'd like to transform gifts list into something like Map<Type,List<Gift>>. I'd like to do it with Java 8 and lambdas in one line. Is it possible?

public class Gift {

    public List<Type> getTypes() {
        return types;
    }

    public Gift withType(Type... types) {
        this.types = Arrays.asList(types);
        return this;
    }

    List<Type> types = new ArrayList<>();
}

public enum Type {
    ADT,
    CHD,
    INF;
}

Previous old code (it looks awfully). That's all what I have.

Map<Type, List<Gift>> byTypes = new HashMap<>();

for (Gift gift : gifts) {
    for (Type type : gift.getTypes()) {
        List<Gift> giftList = byTypes.get(type);
        if (giftList == null) {
            giftList = new ArrayList<>();
        }
        giftList.add(gift);
        byTypes.put(type,giftList);
    }
}

Upvotes: 4

Views: 160

Answers (4)

Lost In time
Lost In time

Reputation: 51

I found other solution with a reduce method. In my opinion smaller one and easier to understand.

      HashMap<Type, List<Gift>> result = gifts.stream().reduce(
            new HashMap<Type, List<Gift>>() {{
                asList(Type.values()).stream().forEach(type -> put(type, new ArrayList<>()));
            }}
            ,
            (map, gift) -> {
                gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
                return map;
            }
            ,
            (map1, map2) -> {
                asList(Type.values()).stream().forEach(type -> map1.get(type).addAll(map2.get(type)));
                return map1;
            }
    );

Upvotes: 0

YoYo
YoYo

Reputation: 9405

Reintroducing your intermediate Pair class P, but in a slightly different way. I think we might expect that in later iterations of Java, this is going to become much more common place and easier to do with the introduction of a built-in Pair type, and the implementation of Value Objects.

package play;

import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static play.Type.*;

public class Play {
  static class P<K,V> { K k; V v; P(K kk,V vv) {k=kk;v=vv;}}
  public static void main(String[] args) throws IOException {
    List<Gift> gifts = new ArrayList<>();
    gifts .add(new Gift().withType(INF, CHD));
    gifts .add(new Gift().withType(ADT, CHD));
    gifts .add(new Gift().withType(INF, ADT));
    Map<Type,List<Gift>> m = gifts.stream()
      .flatMap((g)->g.getTypes().stream().map((t)->new P<>(t,g)))
      .collect(
        Collectors.groupingBy(
          (p)->p.k,
          // Does not work with <code>new EnumMap<>(Type.class)</code>
          // I dont know why, ...
          ()->new EnumMap(Type.class),
          Collectors.mapping(
            (p)->p.v, 
            Collectors.toList() 
          )
        )
      );
    System.out.println("Map: "+m.toString());
  }
}

My only problem in this code is that I cannot explain the need for a 'Untyped' Map as the Map factory. Feel free to explain if you know why ...

Upvotes: 0

Lost In time
Lost In time

Reputation: 51

Ok, I found solution which satify me :-) I wrote my collector :-D

Map<Type, List<Gift>> collect1 = gifts.stream().collect(new TypeToManyGiftCollector());

public class TypeToManyGiftCollector
        implements Collector<Gift, Map<Type, List<Gift>>, Map<Type, List<Gift>>> {

    @Override
    public Supplier<Map<Type, List<Gift>>> supplier() {
        return () -> new HashMap<Type, List<Gift>>() {{
            for (Type type : Type.values()) {
                put(type, new ArrayList<Gift>());
            }
        }};
    }

    @Override
    public BiConsumer<Map<Type, List<Gift>>, Gift> accumulator() {
        return (Map<Type, List<Gift>> map, Gift gift) -> {
            gift.getTypes().stream().forEach(type -> map.get(type).add(gift));
        };
    }

    @Override
    public BinaryOperator<Map<Type, List<Gift>>> combiner() {
        return (Map<Type, List<Gift>> map1, Map<Type, List<Gift>> map2) ->
        {
            for (Type type : Type.values()) {
                map1.get(type).addAll(map2.get(type));
            }
            return map1;
        };
    }

    @Override
    public Function<Map<Type, List<Gift>>, Map<Type, List<Gift>>> finisher() {
        return Function.identity();
    }

    @Override
    public Set<Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(IDENTITY_FINISH));
    }
}

Upvotes: 1

Robert
Robert

Reputation: 452

Using Guava's Multimap:

ListMultimap<Type, Gift> multimap = ArrayListMultimap.create();

gifts.forEach(g -> g.getTypes().forEach(t -> multimap.put(t, g)));

Map<Type, Collection<Gift>> map = multimap.asMap();

Upvotes: 3

Related Questions