How to sum a list of lists per column and per row using lambda functions in Java?

Viewed 1456

I have a list of lists like that:

List<List<Wrapper>> listOfLists = new ArrayList<>();

class Wrapper {
   private int value = 0;
   public int getValue() {
      return value;
   }
}

so it looks like that:

[
[Wrapper(3), Wrapper(4), Wrapper(5)],
[Wrapper(1), Wrapper(2), Wrapper(9)],
[Wrapper(4), Wrapper(10), Wrapper(11)],
]

Is there a concise way to flatten this list of lists like below using lambda functions in Java 8:

(per column): [Wrapper(8), Wrapper(16), Wrapper(25)]
(per row): [Wrapper(12), Wrapper(12), Wrapper(25)]

potentially it could work with different sizes of internal lists:

[
[Wrapper(3), Wrapper(5)],
[Wrapper(1), Wrapper(2), Wrapper(9)],
[Wrapper(4)],
]

this would result to:

(per column): [Wrapper(8), Wrapper(7), Wrapper(9)]
(per row): [Wrapper(8), Wrapper(11), Wrapper(4)]

it seems way more complicated than: Turn a List of Lists into a List Using Lambdas and 3 ways to flatten a list of lists. Is there a reason to prefer one of them?

and what I initially did is similar to although for lists: https://stackoverflow.com/a/36878011/986160

Thanks!

5 Answers

Per row is actually pretty easy:

List<Wrapper> perRow = listOfLists.stream()
            .map(x -> x.stream().mapToInt(Wrapper::getValue).sum())
            .map(Wrapper::new)
            .collect(Collectors.toList());

Per column on the other hand is not that trivial:

private static List<Wrapper> perColumn(List<List<Wrapper>> listOfList) {
    int depth = listOfList.size();
    int max = listOfList.stream().map(List::size).max(Comparator.naturalOrder()).get();
    return IntStream.range(0, max)
            .map(x -> IntStream.range(0, depth)
                    .map(y -> listOfList.get(y).size() < y ? 0 : listOfList.get(y).get(x).getValue())
                    .sum())
            .mapToObj(Wrapper::new)
            .collect(Collectors.toList());
}

If instead of a List<List<Wrapper>> you had a Guava Table, the solution would be a breeze:

Table<Integer, Integer, Wrapper> table = HashBasedTable.create();

table.put(0, 0, new Wrapper(3));
table.put(0, 1, new Wrapper(5));
// TODO fill the table 

List<Wrapper> byRow = table.rowMap().values().stream()
    .map(row -> row.values().stream().mapToInt(Wrapper::getValue).sum())
    .map(Wrapper::new)
    .collect(Collectors.toList());

List<Wrapper> byColumn = table.columnMap().values().stream()
    .map(column -> column.values().stream().mapToInt(Wrapper::getValue).sum())
    .map(Wrapper::new)
    .collect(Collectors.toList());

This code assumes that Wrapper has a constructor that accepts the int value as an argument. Please see Table.rowMap and Table.columnMap docs for further details.

One liners if they might help (wanted to share as comment, too long a text) for List<Wrapper> type of resultPerRow and resultPerColumn :

IntStream.range(0, listOfLists.size()).forEach(
            i -> resultPerRow.add(new Wrapper(listOfLists.get(i).stream().mapToInt(Wrapper::getValue).sum())));

IntStream.range(0, listOfLists.stream().mapToInt(List::size).max().orElse(0))
            .forEach(i -> resultPerColumn.add(new Wrapper(IntStream.range(0, listOfLists.size())
                    .map(y -> listOfLists.get(y).size() < y ? 0 : listOfLists.get(y).get(i).getValue())
                    .sum())));

Check Matrix.straightSum(listOfLists) and Matrix.crossSum(listOfLists) methods.

class Matrix {

    public static List<Wrapper> crossSum(List<List<Wrapper>> input){
        return input.stream().reduce(Matrix::sum).orElse(Collections.emptyList());
    }

    public static List<Wrapper> straightSum(List<List<Wrapper>> input){
        return input.stream().map(Matrix::sum).collect(Collectors.toList());
    }

    private static List<Wrapper> sum(List<Wrapper> big, List<Wrapper> small){
        if (big.size() < small.size()){
            return sum(small, big);
        }
        return IntStream.range(0, big.size()).boxed()
                .map(idx -> sum(big, small, idx))
                .collect(Collectors.toList());
    }

    private static Wrapper sum(List<Wrapper> big, List<Wrapper> small, int idx){
        if (idx < small.size()){
            return new Wrapper(big.get(idx).getValue() + small.get(idx).getValue());
        }
        return big.get(idx);
    }

    private static Wrapper sum(List<Wrapper> line){
        return new Wrapper(line.stream().mapToInt(Wrapper::getValue).sum());
    }
}

My two cents to the solution with a custom function List sum(List,List)

static class Wrapper
{
    public int value = 0;

    public Wrapper( int value )
    {
        super();
        this.value = value;
    }

    public int getValue()
    {
        return value;
    }

    public void setValue( int value )
    {
        this.value = value;
    }

    @Override
    public String toString()
    {
        return "Wrapper [value=" + value + "]";
    }

}

public static List<Wrapper> sum( List<Wrapper> l1, List<Wrapper> l2 )
{
    int l1Size = l1.size();
    int l2Size = l2.size();
    int size = Math.max( l1.size(), l2.size() );
    List<Wrapper> result = new ArrayList<>( size );
    int v1 = 0;
    int v2 = 0;
    for ( int i = 0; i < size; i++ )
    {
        v1 = i < l1Size && l1.get( i ) != null ? l1.get( i ).getValue() : 0;
        v2 = i < l2Size && l2.get( i ) != null ? l2.get( i ).getValue() : 0;
        result.add( new Wrapper( v1 + v2 ) );
    }
    return result;
}

public static void main( String[] args )
    throws Exception
{

    List<List<Wrapper>> listOfLists = new ArrayList<>();
    List<Wrapper> list1 = new ArrayList<>();
    list1.add( new Wrapper( 3 ) );
    list1.add( new Wrapper( 4 ) );
    list1.add( new Wrapper( 5 ) );

    List<Wrapper> list2 = new ArrayList<>();
    list2.add( new Wrapper( 1 ) );
    list2.add( new Wrapper( 2 ) );
    list2.add( new Wrapper( 9 ) );

    List<Wrapper> list3 = new ArrayList<>();
    list3.add( new Wrapper( 4 ) );
    list3.add( new Wrapper( 10 ) );
    list3.add( new Wrapper( 11 ) );

    listOfLists.add( list1 );
    listOfLists.add( list2 );
    listOfLists.add( list3 );

    List<Wrapper> colSum = listOfLists.stream().reduce( Collections.emptyList(), ( l1, l2 ) -> sum( l1, l2 ) );

    List<Wrapper> rowSum =
        listOfLists.stream().map( list -> list.stream().reduce( new Wrapper( 0 ),
                                                                ( w1, w2 ) -> new Wrapper( w1.value
                                                                    + w2.value ) ) ).collect( Collectors.toList() );
    System.out.println( "Sum by column : " );
    colSum.forEach( System.out::println );

    System.out.println( "Sum by row : " );
    rowSum.forEach( System.out::println );

}
Related