4

I'm trying to figure out how to use the Streams API to implement a zip function that takes an unbounded number of int[]'s as an argument; takes the i'th element from each; puts those in a Tuple (obviously a custom Tuple object is needed - which I have) and returns a list of Tuples (i.e. List).

Essentially, for:

{ 1, 2, 3 }
{ 4, 5, 6 }

the proposed method should return: [ Tuple(1, 4), Tuple(2, 5), Tuple(3, 6) ] as a java.util.List<Tuple>

Here is a function that does what I'm trying to do in a "normal" way:

/**
 * Return a list of tuples, where each tuple contains the i-th element
 * from each of the argument sequences.  The returned list is
 * truncated in length to the length of the shortest argument sequence.
 *
 * @param args  the array of ints to be wrapped in {@link Tuple}s
 * @return a list of tuples
 */
public static List<Tuple> zip(int[]... args) {
    List<Tuple> retVal = new ArrayList<>();
    // Find the array with the minimum size
    int minLength = Arrays.stream(args).map(i -> new Integer(i.length)).min((a, b) -> a.compareTo(b)).get();

    for(int i = 0;i < minLength;i++) {
        Tuple.Builder builder = Tuple.builder();
        for(int[] ia : args) {
            builder.add(ia[i]);
        }
        retVal.add(builder.build());
    }

    return retVal;
}
Tunaki
  • 132,869
  • 46
  • 340
  • 423
cogmission
  • 107
  • 8

1 Answers1

4

A solution is to create a Stream over the indexes and using mapToObj to map each int into a Tuple. Also, since you already have a Builder object, we can utilize it to collect the elements into it.

Supposing we add a method Tuple.Builder.addAll(Tuple.Builder other) whose purpose would be to add one builder to another, we could have the following code:

public static List<Tuple> zip(int[]... args) {
    // Find the array with the minimum size
    int minLength = Arrays.stream(args).mapToInt(i -> i.length).min().orElse(0);

    return IntStream.range(0, minLength)
                    .mapToObj(i -> 
                       Arrays.stream(args)
                             .mapToInt(ia -> ia[i])
                             .collect(Tuple::builder, Tuple.Builder::add, Tuple.Builder::addAll)
                             .build()
                    ).collect(Collectors.toList());
}

(If you don't want to support parallel execution, you could just throw an exception with (b1, b2) -> { throw new IllegalStateException(); } and not add the addAll method.)


As a side-note, the code for finding the minimal array size can be simplified: you don't need to box into an Integer, you can just map each array to its length and get the minimum with min(). This returns an OptionalInt; instead of getting its value, which might throw an exception if the Stream was empty, I used orElse(0) so that, in the case of an empty Stream, an empty list is returned.

Tunaki
  • 132,869
  • 46
  • 340
  • 423
  • 1
    If your `Tuple` type supports creating a tuple from an int array, it might be simpler to replace the `collect(…).build()` part of the inner stream by a simple `toArray()` call and create the tuple from the result. – Holger Jan 18 '16 at 10:25