Javaでzipしてみた
KotlinだとふたつのListをzipでかんたんに噛み合わせることができる。Javaにもあったらいいのにな、とずっとおもっていた。
Java22で導入されたGathererを眺めていたら、なんとなくzipできそうな雰囲気があったので、試してみた。
Gatherer
Java22でjava.util.stream.Gathererが導入されました。まだ、previewですが。
windowSlidingをためす
Gatherers#windowSliding()をつかうと、zip的なことができそうですが、ふたつのStreamを噛み合わせることは、できません。
Kotlinで一個ずらして噛み合わせる、そんな感じのことはできる。
$ kotlinc
>>> val list = listOf(1, 2, 3, 4)
>>> list.zip(list.drop(1))
res1: = [(1, 2), (2, 3), (3, 4)]
@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()を使って噛み合わせることができました。
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;
}
}
package sample;
public record Pair<A, B>(
A first,
B second
) {}
test
たとえば、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()を使うと、このように実装できます。
@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]]]
いい感じでつかえそうです。
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());
}
}