はじめに
このシリーズでは、Java
の関数型プログラミングを使いこなすことを目的に、いろいろな題材で練習をしていこうと思います。今回はzipWith
を取り上げます。
zip
とzipWith
zip
は関数型言語にはたいてい標準で装備されている関数で、2つのリストの要素を順々に組み合わせていって新しいリストを作成するものです。
以下はHaskell
の例です。1つ目の要素1
と'a'
によりタプル(1,'a')
が生成され、同様に2番目、3番目の要素が処理されて、全体として長さが3のタプルのリストが生成されます。
Prelude> zip [1,2,3] ['a','b','c']
[(1,'a'),(2,'b'),(3,'c')]
zipWith
は要素同士を組み合わせる際に適用する関数を指定できる高階関数です。
以下の例では、指定した要素を指定した回数繰り返して複製するreplicate
という関数を適用した例です。
Prelude> zipWith replicate [1,2,3] ['a','b','c']
["a","bb","ccc"]
Java
でzipWith
を実装
最初はわかりやすさのためジェネリクスを使わずに汎用的でない_zipWith
を書いてみます。
前節のHaskell
のzipWith
のコード例と同等のテストコードは以下となります。
@Test
public void test_zipWith() {
List<String> l1 = Arrays.asList("a", "b", "c");
List<Integer> l2 = Arrays.asList(new Integer(1), new Integer(2), new Integer(3));
BiFunction<String, Integer, String> f = FunctionsTest::replicate;
List<String> zipped = Functions._zipWith(f, l1, l2);
assertNotNull(zipped);
assertEquals(3, zipped.size());
assertEquals("a", zipped.get(0));
assertEquals("bb", zipped.get(1));
assertEquals("ccc", zipped.get(2));
}
private static String replicate(String str, Integer n) {
return IntStream.range(0, n).mapToObj(i -> str).collect(Collectors.joining());
}
_zipWith
の実装は以下のようになります。
public static List<String> _zipWith(BiFunction<String, Integer, String> f, List<String> l1, List<Integer> l2) {
final int min;
if ((l1 == null || l1.size() == 0) || (l2 == null || l2.size() == 0)) {
return Collections.emptyList();
} else {
min = l1.size() <= l2.size() ? l1.size() : l2.size();
}
return IntStream.range(0, min)
.mapToObj(i -> f.apply(l1.get(i), l2.get(i)))
.collect(Collectors.toList());
}
あとはジェネリクスを使って汎用化すればよいですね。
public static <T, U, R> List<R> zipWith(BiFunction<T, U, R> f, List<T> l1, List<U> l2) {
final int min;
if ((l1 == null || l1.size() == 0) || (l2 == null || l2.size() == 0)) {
return Collections.emptyList();
} else {
min = l1.size() <= l2.size() ? l1.size() : l2.size();
}
return IntStream.range(0, min)
.mapToObj(i -> f.apply(l1.get(i), l2.get(i)))
.collect(Collectors.toList());
}
Java
でzip
を実装
zipWith
を使用してzip
を実装できます。
※残念ながらJava
にはタプルがないので、以下のコードではjava.util.AbstractMap.SimpleEntry
で代用しています。
public static <T, U> List<SimpleEntry<T, U>> zip(List<T> l1, List<U> l2) {
return zipWith((t, u) -> new SimpleEntry<>(t, u), l1, l2);
}
zipWith
に渡す関数を引数を2つ受け取ってタプル(ここではSimpleEntry)を生成する関数
ととらえて、ラムダ式で記述しています。
補足
サンプルコードはGitHubにアップしています。