Javaでzipしてみた


# Javaでzipしてみた

KotlinだとふたつのListをzipでかんたんに噛み合わせることができる。Javaにもあったらいいのにな、とずっとおもっていた。
Java22で導入されたGathererを眺めていたら、なんとなくzipできそうな雰囲気があったので、試してみた。

## Gatherer
Java22でjava.util.stream.Gathererが導入されました。まだ、previewですが。
* [java.util.stream.Gatherer](https://docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/java.base/java/util/stream/Gatherer.html)
* [JEP 461: Stream Gatherers (Preview)](https://openjdk.org/jeps/461)

## windowSlidingをためす

Gatherers#windowSliding()をつかうと、zip的なことができそうですが、ふたつのStreamを噛み合わせることは、できません。
* [Gatherers#windowSliding()](https://docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/java.base/java/util/stream/Gatherers.html)

Kotlinで一個ずらして噛み合わせる、そんな感じのことはできる。

```
$ kotlinc
>>> val list = listOf(1, 2, 3, 4)
>>> list.zip(list.drop(1))
res1: = [(1, 2), (2, 3), (3, 4)]
```

```java
  @Test
public void aa() {
  final var list = List.of(1,2,3,4);
  final var result = list.stream().gather(Gatherers.windowSliding(2)).toList();
  System.out.println(result);
}
// [[1, 2], [2, 3], [3, 4]]
```

## zipを実装してみる

ふたつのStream、とはいけませんでしたが、StreamとIteratorであれば、Gatherer#ofSequential()を使って噛み合わせることができました。

* [Gatherer#ofSequential()](https://docs.oracle.com/en%2Fjava%2Fjavase%2F22%2Fdocs%2Fapi%2F%2F/java.base/java/util/stream/Gatherer.html#ofSequential(java.util.function.Supplier,java.util.stream.Gatherer.Integrator))

```java
package sample;

import java.util.Iterator;
import java.util.stream.Gatherer;

/**
 */
public class MyGatherers {
  private MyGatherers() {}

  /**
   * @param otherIterator
   * @param <A>
   * @param <B>
   * @return
   */
  public static <A, B> Gatherer<A, Iterator<B>, Pair<A, B>> zip(Iterator<B> otherIterator) {
    final Gatherer<A, Iterator<B>, Pair<A, B>> gatherer = Gatherer.ofSequential(
        //
        () -> otherIterator,
        //
        Gatherer.Integrator.ofGreedy((iterator, first, downstream) -> {
          final boolean hasNext = iterator.hasNext();
          final B second = hasNext ? otherIterator.next() : null;

          //
          return hasNext ? downstream.push(new Pair<>(first, second)) : false;
        }));

    return gatherer;
  }
}
```

```java
package sample;

public record Pair<A, B>(
    A first,
    B second
) {}
```

### test

たとえば、Kotlinのこのような処理。

```kotlin
fun main() {
  data class Person(val id: String, val name: String)

  val listA = (1..10).map{Math.random()}
  val result = listA.zip(listOf(Person("a", "Alice"), Person("b", "Bob"), Person("c", "Carol")))
  println(result)
}

// [(0.5695021953159504, Person(id=a, name=Alice)), 
//  (0.6565867616482192, Person(id=b, name=Bob)),
//  (0.6789263224602792, Person(id=c, name=Carol))]
```

これを、定義したMyGatherers#zip()を使うと、このように実装できます。

```java
  @Test
  void zip_21() {
    record Person(String id, String name) {}

    final var result = IntStream.range(1, 10)
        .mapToDouble(it -> Math.random())
        .boxed()
        .gather(MyGatherers.zip(
            List.of(new Person("a", "Alice"), new Person("b", "Bob"), new Person("c", "Carol")).listIterator()))
        .toList();

    System.out.println(result);
    Assertions.assertEquals(3, result.size());
  }
// [Pair[first=0.0827916056379453, second=Person[id=a, name=Alice]],
//  Pair[first=0.2503321398502284, second=Person[id=b, name=Bob]],
//  Pair[first=0.1072768883956946, second=Person[id=c, name=Carol]]]
```

いい感じでつかえそうです。


```java
package sample;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

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

class MyGatherersTest {
  
  @Test
  void zip_01() {
    final var listA = List.of(1, 2, 3);
    final var listB = List.of("aa", "kk", "ss", "tt");

    final List<Pair<Integer, String>> result =
        listA.stream()
            .gather(MyGatherers.zip(listB.listIterator()))
            .toList();

    System.out.println(result);
    Assertions.assertEquals(3, result.size());
  }


  @Test
  void zip_02() {
    final List<Integer> listA = List.of();
    final var listB = List.of("aa", "kk", "ss", "tt");

    final List<Pair<Integer, String>> result =
        listA.stream()
            .gather(MyGatherers.zip(listB.listIterator()))
            .toList();

    System.out.println(result);
    Assertions.assertTrue(result.isEmpty());
  }

  @Test
  void zip_03() {
    final var listA = List.of(1, 2, 3);
    final List<String> listB = List.of();

    final List<Pair<Integer, String>> result =
        listA.stream()
            .gather(MyGatherers.zip(listB.listIterator()))
            .toList();

    System.out.println(result);
    Assertions.assertTrue(result.isEmpty());
  }

  @Test
  void zip_21() {
    record Person(String id, String name) {
    }

    final var result = IntStream.range(1, 10)
        .mapToDouble(it -> Math.random())
        .boxed()
        .gather(MyGatherers.zip(
            List.of(new Person("a", "Alice"), new Person("b", "Bob"), new Person("c", "Carol")).listIterator()))
        .toList();

    System.out.println(result);
    Assertions.assertEquals(3, result.size());
  }
}
```