2

I need to do some Matrix work in an efficient and flexible way and hoped I could practice my Java 8 using streams and lambdas and perhaps even get free parallelism out of it. One point I am struggling with is how to perform an operation on two streams putting the result into a third.

Consider the simple mechanism:

static final List<String> a = Arrays.asList("A", "A", "A");
static final List<String> b = Arrays.asList("B", "B", "B");

public void withoutStreams() {
    // The boring old way.
    List<String> c = new ArrayList<>();
    for (Iterator<String> ai = a.iterator(), bi = b.iterator(); ai.hasNext() && bi.hasNext();) {
        c.add(ai.next() + bi.next());
    }
    System.out.println(c);
}

Works fine but I want to use Streams.

private void withStreams() {
    List<String> c = new ArrayList<>();
    combine(a.stream(), b.stream(), c, (String x, String y) -> x + y);
}

private void combine(Stream<String> a, Stream<String> b, List<String> c, BinaryOperator<String> op) {
    // What can I do here to make it happen?
}

I fully expect we will be filling c using a Consumer of some form but extra kudos for coming up with some way of referring to a specific cell of the matrix other than using (row,col) bearing in mind that the cells will be immutable.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
OldCurmudgeon
  • 64,482
  • 16
  • 119
  • 213
  • 1
    This could be probably be achieved with a zip function, which unfortunately was removed the JDK 8 libraries at the end. But in [this other answer](http://stackoverflow.com/q/17640754/697630) you can find some interesting alternatives. – Edwin Dalorzo Mar 25 '15 at 15:53

2 Answers2

3

You can use the IntStream class to simulate indexing and then .mapToObj to concatenate the corresponding to the index objects from a and b:

List<String> list = IntStream.range(0, Math.max(a.size(), b.size()))
                             .mapToObj(i -> a.get(i) + b.get(i))
                             .collect(Collectors.toList());

Applied to your method, this will look like:

private void combine(List<String> a, List<String> b, List<String> c, BinaryOperator<String> op) {
    c = IntStream.range(0, Math.max(a.size(), b.size()))
                 .mapToObj(i -> op.apply(a.get(i), b.get(i)))
                 .collect(Collectors.toList());
}

However, if you don't want to change the method's signature, here is a solution which works for all possible combinations of infinite and finite streams:

private void combine(Stream<String> a, Stream<String> b, List<String> c, BinaryOperator<String> op) {
    Iterator<String> i1 = a.iterator();
    Iterator<String> i2 = b.iterator();
    Iterable<String> i = () -> new Iterator<String>() {
        public boolean hasNext() {
            return i1.hasNext() && i2.hasNext();
        }
        public String next() {
            return op.apply(i1.next(), i2.next());
        }
    };
    c = StreamSupport.stream(i.spliterator(), false).collect(Collectors.toList());
}
Community
  • 1
  • 1
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
0

Functional programming style, using recursive (no loop):

static Stream<String> combine(List<String> a, List<String> b) {
    if(a.isEmpty() ||  b.isEmpty()) {
        return Stream.empty();
    }

    return Stream.concat(
            Stream.of(a.get(0) + b.get(0)),
            combine(a.stream().skip(1).collect(Collectors.toList()), 
                    b.stream().skip(1).collect(Collectors.toList()))
            );
}

plus: I vote up kocko's answer, my answer is for fun.

卢声远 Shengyuan Lu
  • 31,208
  • 22
  • 85
  • 130