Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to expand and do regroup a List of List using Java 8 Stream?

I have a list of the Class A, that includes a List itself.

public class A {
    public double val;
    public String id;
    public List<String> names = new ArrayList<String>();
    public A(double v, String ID, String name)
    {
        val = v;
        id = ID;
        names.add(name);
    }

static public List<A> createAnExample()
{
    List<A> items  = new ArrayList<A>();

    items.add(new A(8.0,"x1","y11"));
    items.add(new A(12.0, "x2", "y21"));
    items.add(new A(24.0,"x3","y31"));
    items.get(0).names.add("y12");
    items.get(1).names.add("y11");
    items.get(1).names.add("y31");
    items.get(2).names.add("y11");
    items.get(2).names.add("y32");
    items.get(2).names.add("y33");
    return  items;
}

The aim is to sum over average val per id over the List. I added the code in Main function by using some Java 8 stream. My question is how can I rewrite it in a more elegant way without using the second Array and the for loop.

static public void main(String[] args) {
    List<A> items = createAnExample();

    List<A> items2 = new ArrayList<A>();
    for (int i = 0; i < items.size(); i++) {
        List<String> names = items.get(i).names;
        double v = items.get(i).val / names.size();
        String itemid = items.get(i).id;
        for (String n : names) {
            A item = new A(v, itemid, n);
            items2.add(item);
        }
    }
    Map<String, Double> x = items2.stream().collect(Collectors.groupingBy(item ->
            item.names.isEmpty() ? "NULL" : item.names.get(0), Collectors.summingDouble(item -> item.val)));
    for (Map.Entry entry : x.entrySet())
        System.out.println(entry.getKey() + " --> " + entry.getValue());
}
like image 605
shahram cohen Avatar asked Aug 31 '15 23:08

shahram cohen


2 Answers

You can do it with flatMap:

x = items.stream()
    .flatMap(a -> a.names.stream()
        .map(n -> new AbstractMap.SimpleEntry<>(n, a.val / a.names.size()))
    ).collect(groupingBy(
        Map.Entry::getKey, summingDouble(Map.Entry::getValue)
    ));

If you find yourself dealing with problems like these often, consider a static method to create a Map.Entry:

static<K,V> Map.Entry<K,V> entry(K k, V v) {
    return new AbstractMap.SimpleImmutableEntry<>(k,v);
}

Then you would have a less verbose .map(n -> entry(n, a.val/a.names.size()))

like image 128
Misha Avatar answered Oct 01 '22 08:10

Misha


In my free StreamEx library which extends standard Stream API there are special operations which help building such complex maps. Using the StreamEx your problem can be solved like this:

Map<String, Double> x = StreamEx.of(createAnExample())
    .mapToEntry(item -> item.names, item -> item.val / item.names.size())
    .flatMapKeys(List::stream)
    .grouping(Collectors.summingDouble(v -> v));

Here mapToEntry creates stream of map entries (so-called EntryStream) where keys are lists of names and values are averaged vals. Next we use flatMapKeys to flatten the keys leaving values as is (so we have stream of Entry<String, Double>). Finally we group them together summing the values for repeating keys.

like image 33
Tagir Valeev Avatar answered Oct 01 '22 08:10

Tagir Valeev