6

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!

Michail Michailidis
  • 11,792
  • 6
  • 63
  • 106
  • @nullpointer yeap obviously :) thanks - although with this approach it might be flexible.. if one is shorter it won't contribute to the sum. in other words the resulting list might be as long as the max of rows or the max of columns number – Michail Michailidis Nov 08 '17 at 08:37
  • A sample prior to Java8 would be good to understand what you're *trying* to achieve. – Naman Nov 08 '17 at 08:41
  • @nullpointer I would need double `for` loops and a temp list to += the results for each column sum - pretty standard. Assuming I would know the max width/height so it won't go out of bounds – Michail Michailidis Nov 08 '17 at 08:44
  • why 3 down-votes? – Michail Michailidis Nov 08 '17 at 09:28
  • 2
    @MichailMichailidis because u have not tried anything yourself probably... people don't usually like this over here – Eugene Nov 08 '17 at 09:30
  • 1
    @Eugene obviously I could do that with two for loops both for rows and columns but those are already posted here in Stackoverlow - if I knew how to do it I wouldn't even ask! Plus when I down vote I also give feedback – Michail Michailidis Nov 08 '17 at 09:33
  • 2
    @MichailMichailidis right, unfortunately there is not requirement to leave a comment when u downvote, you just live with it – Eugene Nov 08 '17 at 09:35
  • @Eugene I listed the links on what I based my previous solution to hoping I won't get a fifth down-vote for just being curious about how something like that can be done with lambda functions – Michail Michailidis Nov 08 '17 at 09:41
  • 1
    I know that you haven't asked, but Guava has a [`Table`](https://github.com/google/guava/wiki/NewCollectionTypesExplained#table) data structure that is ideal for these cases... – fps Nov 08 '17 at 14:13
  • @FedericoPeraltaSchaffner oh that's interesting thanks! – Michail Michailidis Nov 08 '17 at 15:24
  • @Eugene Thanks for your support, I've already asked. – fps Nov 08 '17 at 16:38

5 Answers5

7

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());
}
Eugene
  • 117,005
  • 15
  • 201
  • 306
  • 2
    the row par can be more straightforward by mapping the row to IntStream listOfList.stream().map(row -> row.stream().mapToInt(Wrapper::getValue).sum()).collect(Collectors.toList()); You can also change listOfList.stream().count() by listOfList.size() it's more efficient – JEY Nov 08 '17 at 09:18
  • 2
    `depth = listOfList.size()` ? – Naman Nov 08 '17 at 09:21
  • @JEY quite right, I actually coded the one that you suggested, but pasted the wrong one :| – Eugene Nov 08 '17 at 09:25
  • 1
    @nullpointer indeed - I got so excited by the question (love this riddles!) that totally did this stupidity - my bad and and thank u – Eugene Nov 08 '17 at 09:26
  • 1
    `max = listOfList.stream().mapToInt(List::size).max().orElse(0)` ? – Naman Nov 08 '17 at 09:34
  • thank you @Eugene for the effort I will have a look into it – Michail Michailidis Nov 08 '17 at 09:38
3

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.

fps
  • 33,623
  • 8
  • 55
  • 110
2

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())));
Naman
  • 27,789
  • 26
  • 218
  • 353
2

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());
    }
}
Adrian
  • 31
  • 2
1

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 );

}
Mahendra
  • 1,436
  • 9
  • 15