2

My problem basically boils down to reducing a List into a linked list, but the inferred types from the reduce function don't seem right.

My list will look like this

[0, 1, 2]

I expect the reduce function to do this at each reduce step

null                            // identity (a Node)
Node(0, null)                   // Node a = null, int b = 0
Node(1, Node(0, null))          // Node a = Node(0, null), int b = 1
Node(2, Node(1, Node(0, null))) // Node a = Node(1, Node(0, null)), int b = 2

However, the reduce function seems to think that this won't work because I guess it doesn't think the identity is a Node.

Here is my code.

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Example {
    static class Node {
        int value;
        Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }
    }

    static Node reverse(List<Integer> list) {
        return list.stream()
                .reduce(null, (a, b) -> new Node(b, a)); // error: thinks a is an integer
    }

    void run() {
        List<Integer> list = IntStream.range(0, 3)
                .boxed()
                .collect(Collectors.toList());
        Node reversed = reverse(list);
    }

    public static void main(String[] args) {
        new Example().run();
    }
}

What am I doing wrong?

EDIT After the accepted answer, my code looks like this:

import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Example {
    static class Node {
        int value;
        Node next;

        public Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }

        @Override
        public String toString() {
            return "Node{" +
                    "value=" + value +
                    ", next=" + next +
                    '}';
        }
    }

    static Node reverse(List<Integer> list) {
        return list.stream()
                .reduce(null, (n, i) -> {
                    System.out.println("Will happen"); // to demonstrate that this is called
                    return new Node(i, n);
                }, (n1, n2) -> {
                    System.out.println("Won't happen"); // and this never is
                    return new Node(n1.value, n2);
                });
    }

    void run() {
        List<Integer> list = IntStream.range(0, 3)
                .boxed()
                .collect(Collectors.toList());
        Node reversed = reverse(list);
        System.out.println(reversed);
    }

    public static void main(String[] args) {
        new Example().run();
    }
}

And it now prints

Will happen
Will happen
Will happen
Node{value=2, next=Node{value=1, next=Node{value=0, next=null}}}

I still don't know why Java can't tell that third argument to the reduce function is unnecessary and it will never get called, but that's a question for another day.

Second Edit

It's possible to just create a new method for reduce operations like this because the third argument to reduce can just be a function that does nothing.

static <T, U> U reduce(Stream<T> stream, U identity, BiFunction<U, ? super T, U> accumulator) {
    return stream.reduce(identity, accumulator, (a, b) -> null);
}

static Node reverse(List<Integer> list) {
    return reduce(list.stream(), null, (n, i) -> new Node(i, n));
}
michaelsnowden
  • 6,031
  • 2
  • 38
  • 83
  • 1
    Why not use `collect` instead of `reduce`? `collect` is more appropriate for creating a Collection from a Stream. – Eran Jul 03 '16 at 07:09
  • @Eran I didn't like that it required me to implement 3 methods – michaelsnowden Jul 03 '16 at 07:13
  • 2
    @michaelsnowden Try to run the `Stream` in parallel then you need the combiner. – Flown Jul 03 '16 at 08:47
  • 1
    Instead of **reduce** you're really looking for a **fold** operation, which unfortunately isn't supported in Java at this time. See [this answer](http://stackoverflow.com/a/24316429/1441122). You can simulate a **fold** operation using `forEachOrdered`. – Stuart Marks Jul 03 '16 at 18:20

2 Answers2

5

You can use the other reduce operator, doing

    static Node reverse(List<Integer> list) {
      return list.stream()
        .reduce(
          (Node) null, //the empty element
          (n, i) -> new Node(i, n) , //combining a Node and an Integer
          (n1, n2) -> new Node(n1.value, n2)); // could be anything
    }

Edit: To make it work with parallelStream:

    public static Node merge(Node n1, Node n2) {
        if (n1 == null) {
            return n2;
        } else {
            return new Node(n1.value, merge(n1.next, n2));
        }
    }

    static Node reverse(List<Integer> list) {
      return list.stream()
        .reduce(
          (Node) null, //the empty element
          (n, i) -> new Node(i, n) , //combining a Node and an Integer
          (n1, n2) -> merge(n1, n2)); // combining two Nodes
    }
CoronA
  • 7,717
  • 2
  • 26
  • 53
  • 2
    Note that this doesn't work in parallel. Using `.parallelStream()` inside `reverse` instead of `stream()` will result in a wrong result. – Tunaki Jul 03 '16 at 14:06
  • Thanks, the third argument was completely irrelevant in this scenario. For completeness I added a method that makes it work for `parallelStream()`. Yet this scenario is not very suited for the parallel stream. – CoronA Jul 04 '16 at 10:02
3

The problem you have is that reduce is expect to return the same type it accumulates. In this case null is an Integer as is a

What you can do is map each Integer to a Node and then reduce the Nodes into a linked list.

static Node reverse(List<Integer> list) {
    return list.stream()
            .map(i -> new Node(i, null))
            .reduce(null, (a, b) -> {
                b.next = a;
                return b;
            });
}

void run() {
    List<Integer> list = IntStream.range(0, 3)
            .boxed()
            .collect(Collectors.toList());
    Node reversed = reverse(list);
    for(Node n = reversed; n != null ; n = n.next)
        System.out.println(n.value);
}

prints

2
1
0
Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130