Edited at

LINQ to Objects と Java8-Stream API と Kotlin の対応表

More than 1 year has passed since last update.

Java8 で filtermap が使えるようになったー!

というわけで .NET の LINQ to Objects との対応表を作ってみました。

2018.2.7 - Kotlin も追記しました!

の比較です。 kotlin.collections は遅延ではないので注意です。

Java の方は

も使います。

まだ試したものは少ないので間違ってるかもしれない & カテゴライズが適当 なので、編集リクエストしてもらえるとありがたいです。

機能
LINQ
Java8
Kotlin

【基本的なやつ】

 

抽出
Where
filter
filter

射影
Select
map
map

並べ替え
OrderBy / OrderByDescending
sorted
sortBy / sortByDescending /

後続を並べ替え(2次ソート条件)
ThenBy / ThenByDescending
n/a
n/a

平坦化して射影
SelectMany
flatMap
flatMap

【抽出系】

 

n件飛ばす
Skip
skip
drop

条件を満たすまで飛ばす
SkipWhile
n/a
dropWhile

n件まで流す
Take
limit
take

条件を満たすまで流す
TakeWhile
n/a
takeWhile

【合成系】

 

連結
Concat
concat
plus

積集合
Intersect
n/a
intersect

和集合
Union
n/a
union

差集合
Except
n/a
subtract

内部結合
Join
n/a
n/a

外部結合
GroupJoin
n/a
n/a

並びを逆にする
Reverse
n/a
reverse

2つの値を揃えて流す
Zip
n/a
zip

【グループ化、集計系】

 

重複を無くす
Distinct
distinct
distinct

畳み込み
Aggregate
reduce
reduce / fold

グループ化
GroupBy
Collectors.groupingBy
groupBy

平均
Average
IntStream.average / Collectors.summarizingXXX
average

件数
Count / LongCount
count
count

最大
Max
max
max

最小
Min
min
min

合計
Sum
IntStream.sum / Collectors.summarizingXXX
sum

先頭
First / FirstOrDefault
findFirst
first / firstOrNull

終端
Last / LastOrDefault
n/a
last / lastOrNull

とりあえず値を得る

findAny
 

集計用の汎用関数?

collect
 

1件の値を得る
Single / SingleOrDefault

 

空なら既定値を返す
DefaultIfEmpty

 

全データが条件にマッチするか?
All
allMatch
all

いずれかのデータが条件にマッチするか?
Any
anyMatch
any

いずれかのデータも条件にマッチしないか?

noneMatch
none

【生成系】

 

空っぽ
Empty
empty
emptyArray

範囲を生成
Range
n/a
XxxProgression(例: IntProgression)

繰り返す
Repeat
n/a
n/a

無限リスト生成

generate / iterate
generateSequence

【その他】

 

SequenceEqual

 

列挙
ToList().ForEach
forEach
forEach

なんか Action を挟む(デバッグ用?)

peek
 

ううむ、合成系の機能はほとんどないようです…ので自力でやるしか。

以下、サンプル。


サンプル

LINQ の方は Mac+Mono(Xamarin) で試しています(ぼそり


抽出(Where)、並べ替え(OrderBy)、射影(Select)

0〜9 を、偶数値だけ抽出して、降順にソートして、値を10倍して、出力。


C#

Enumerable.Range(0, 10)

.Where(x => x % 2 == 0)
.OrderByDescending(x => x)
.Select(x => x * 10)
.ToList().ForEach(Console.WriteLine);


Java

Arrays.asList(0,1,2,3,4,5,6,7,8,9).stream()

.filter(x -> x % 2 == 0)
.sorted((x, y) -> y - x)
.map(x -> x * 10)
.forEach(System.out::println);


Kotlin

arrayOf(0,1,2,3,4,5,6,7,8,9)

.filter { x -> x % 2 == 0 }
.sortedByDescending { it }
.map { it * 10 }
.forEach { System.out.println(it) }

80 60 40 20 0


平坦化して射影(SelectMany)

1〜5のリストから、「n×10から始まるn件」のリストを生成。(結果見たほうが分かりやすいな(^_^;)


C#

Enumerable.Range(1, 5)

.SelectMany(x => Enumerable.Range(10 * x, x))
.ToList().ForEach(Console.WriteLine);


Java

Arrays.asList(1,2,3,4,5).stream()

.flatMap(x -> IntStream.range(x * 10, x * 10 + x).boxed())
.forEach(System.out::println);


Kotlin

arrayOf(1,2,3,4,5)

.flatMap { x -> IntProgression.fromClosedRange(x * 10, x * 10 + x - 1, 1)}
.forEach { System.out.println(it) }

10 

20 21
30 31 32
40 41 42 43
50 51 52 53 54


抽出系(Take, Skip)

1〜10のリストの3件飛ばして、5件取得。


C#

Enumerable.Range(1, 10)

.Skip(3)
.Take(5)
.ToList().ForEach(Console.WriteLine);


Java

// 無限リストでも limit あるから大丈夫

Stream.iterate(1, x-> x++)
.skip(3)
.limit(5)
.forEach(System.out::println);


Kotlin

generateSequence(1) { it + 1 }

.drop(3)
.take(5)
.forEach { System.out.println(it) }

4 5 6 7 8

LINQ には件数でなく条件を指定できる TakeWhile SkipWhile がありますが、Java にはなさそうなので filter で代用しないといけなさそう。


C#

Enumerable.Range(1, 10)

.SkipWhile(x => x < 4)
.TakeWhile(x => x < 9)
.ToList().ForEach(Console.WriteLine);

Kotlin には dropWhiletakeWhile があります。


Kotlin

generateSequence(1) { it + 1 }

.dropWhile { it < 4 }
.takeWhile { it < 9 }
.forEach { System.out.println(it) }


連結(Concat)

2つのリストをつなげる


C#

new int[] { 1, 2, 3 }.Concat(new int[]{ 30, 20, 10 })

.ToList().ForEach(Console.WriteLine);


Java

Stream.concat(

Arrays.asList(1,2,3).stream(),
Arrays.asList(30,20,10).stream())
.forEach(System.out::println);

なんで static メソッドやねん…。


Kotlin

arrayOf(1,2,3)

.plus(arrayOf(30,20,10))
.forEach { System.out.println(it) }

1 2 3 30 20 10


積集合(Intersect)、和集合(Union)、差集合(Except)

積集合:2つのリストから重複をなくす。

和集合:2つのリストをマージする。

差集合:リスト1を基準にリスト2との差分を得る。


C#

var list1 = new int[]{1,2,3,4,5,6};

var list2 = new int[]{8,7,6,5,4};

list1.Intersect(list2)
.ToList().ForEach(Console.WriteLine);

list1.Union(list2)
.ToList().ForEach(Console.WriteLine);

list1.Except(list2)
.ToList().ForEach(Console.WriteLine);



Java

// 自力で実現かよw

list1.stream().filter(x -> list2.stream().anyMatch(y -> y == x))
.forEach(System.out::println);

Stream.concat(list1.stream(),
list2.stream().filter(x -> list1.stream().noneMatch(y -> y == x)))
.forEach(System.out::println);

list1.stream().filter(x -> list2.stream().noneMatch(y -> y == x))
.forEach(System.out::println);



Kotlin

val list1 = arrayOf(1,2,3,4,5,6).asIterable()

val list2 = arrayOf(8,7,6,5,4).asIterable()

list1.intersect(list2)
.forEach(System.out::println)

list1.union(list2)
.forEach(System.out::println)

list1.subtract(list2)
.forEach(System.out::println)


4 5 6 // 積

1 2 3 4 5 6 8 7 // 和
1 2 3 // 差


内部結合(Join)

商品マスタと売上テーブルを INNER JOIN する的な。


C#

var master = new [] {

new { Id = 1, Name = "Apple" },
new { Id = 2, Name = "Grape" }
};

var sales = new [] {
new { Id = 1, Sales = 100 },
new { Id = 2, Sales = 200 },
new { Id = 2, Sales = 300 },
new { Id = 3, Sales = 400 },
};

master.Join(sales,
outer=>outer.Id,
inner=>inner.Id,
(o, i) => new { o.Name, i.Sales })
.ToList().ForEach(Console.WriteLine);



Java

// 自力

List<Pair<Integer, String>> master = Arrays.asList(
new Pair<>(1, "Apple"),
new Pair<>(2, "Grape")
);

List<Pair<Integer, Integer>> sales = Arrays.asList(
new Pair<>(1, 100),
new Pair<>(2, 200),
new Pair<>(2, 300),
new Pair<>(3, 400)
);

master.stream()
.flatMap(outer -> sales.stream()
.filter(inner -> outer.getKey() == inner.getKey())
.map(z-> new Pair<String, Integer>(outer.getValue(), z.getValue())))
.forEach(System.out::println);



Kotlin

val master = arrayOf(

Pair(1, "Apple"),
Pair(2, "Grape"))

val sales = arrayOf(
Pair(1, 100),
Pair(2, 200),
Pair(2, 300),
Pair(3, 400))

// 自力でやるしかなさそう・・・
master
.flatMap { outer -> sales.filter { inner -> outer.first == inner.first }
.map { z -> Pair(outer.second, z.second) }}
.forEach(System.out::println);


{ Name = Apple, Sales = 100 }

{ Name = Grape, Sales = 200 }
{ Name = Grape, Sales = 300 }


外部結合(GroupJoin)

商品マスタと売上テーブルを OUTER JOIN する的な。結合先のテーブルに行が見つからなかったものは null になる。


C#

var master = new [] {

new { Id = 1, Name = "Apple" },
new { Id = 2, Name = "Grape" },
new { Id = 5, Name = "Orange" },
};

var sales = new [] { // Orange は無い
new { Id = 1, Sales = 100},
new { Id = 2, Sales = 200},
new { Id = 3, Sales = 400},
};

master.GroupJoin(sales,
outer=>outer.Id,
inner=>inner.Id,
(o, i) => new { o.Name, FirstOfSales = i.Select(
x=>(int?)x.Sales).FirstOrDefault() }) // 無かったら null にしたいので null許容型にしてから FirstOrDefault
.ToList().ForEach(Console.WriteLine);


たぶん普通は First じゃなくて Sum とか使うんだろう。


Java

// これも自力

List<Pair<Integer, String>> master = Arrays.asList(
new Pair<>(1, "Apple"),
new Pair<>(2, "Grape"),
new Pair<>(5, "Orange")
);

List<Pair<Integer, Integer>> sales = Arrays.asList(
new Pair<>(1, 100),
new Pair<>(2, 200),
new Pair<>(2, 300),
new Pair<>(3, 400)
);

master.stream().map(outer->new Pair<String, Optional<Integer>>(outer.getValue(),
sales.stream()
.filter(inner->inner.getKey() == outer.getKey()) // Id でフィルタ
.map(x->x.getValue()) // Sales だけに射影
.findFirst())) // 同一Id中の先頭
.forEach(System.out::println);



Kotlin

val master = arrayOf(

Pair(1, "Apple"),
Pair(2, "Grape"),
Pair(5, "Orange"))

val sales = arrayOf(
Pair(1, 100),
Pair(2, 200),
Pair(3, 400))

// これも自力かー
master.map { outer -> Pair(outer.second,
sales.filter { inner -> inner.first == outer.first } // Id でフィルタ
.map { x -> x.second } // Sales だけに射影
.firstOrNull()) } // 同一Id中の先頭(相手が居なかったら null)
.forEach(System.out::println);


[.NET]

{ Name = Apple, FirstOfSales = 100 }
{ Name = Grape, FirstOfSales = 200 }
{ Name = Orange, FirstOfSales = } // 相手が居ないやつは null になる

[Java]
Apple=Optional[100]
Grape=Optional[200]
Orange=Optional.empty // Option だから empty になるのは良い

[Kotlin]
(Apple, 100)
(Grape, 200)
(Orange, null) // firstOrNull だから nullable


2つの値を揃えて流す(Zip)

2つのリストの値をひとつずつセットにして流す。


C#

var arr1 = new int[] { 1, 2, 3, 4, 5 };

var arr2 = new string[] { "hoge", "fuga", "piyo" };

arr1.Zip(arr2, (x, y) => new {x, y})
.ToList()
.ForEach(Console.WriteLine);



Java

// FIXME どうやるの? Streams.zip はどこいった?



Kotlin

val arr1 = arrayOf(1,2,3,4,5)

val arr2 = arrayOf("hoge", "fuga", "piyo")

arr1.zip(arr2) { x, y -> Pair(x, y) }
.forEach { System.out.println(it) }


{ x = 1, y = hoge }

{ x = 2, y = fuga }
{ x = 3, y = piyo }


重複を無くす(Distinct)

重複する数値リストから重複をなくす。


C#

new int[]{1,3,4,3,2,4}

.Distinct()
.ToList().ForEach(Console.WriteLine);


Java

Arrays.asList(1,3,4,3,2,4).stream()

.distinct()
.forEach(System.out::println);


Kotln

arrayOf(1,3,4,3,2,4)

.distinct()
.forEach { System.out.println(it) }

1 3 4 2


畳み込み

いろいろな集計の素、畳み込み。言語により fold とか reduce とか aggregate とか、いろいろな呼び名がありますね。

よい例が浮かなかったので Max を実装してみました。


C#

var max = new int[]{1,5,3,7,2,4}

.Aggregate(Int32.MinValue, (x, y) => Math.Max(x, y));
Console.WriteLine(max);


Java

int max = Arrays.asList(1,5,3,7,2,4).stream()

.reduce(Integer.MIN_VALUE, (x, y) -> Math.max(x, y));
System.out.println(max);


Kotlin

// reduce もあるけど fold もあるよ

val max = arrayOf(1, 5, 3, 7, 2, 4)
.fold(Int.MIN_VALUE) { x, y -> Math.max(x, y) }
System.out.println(max)

7


グループ化

リストの要素をキーにしてグループ化する。Salesは合計を計算する。


C#

var sales = new [] { 

new { Id = 1, Sales = 100 },
new { Id = 2, Sales = 200 },
new { Id = 2, Sales = 300 },
new { Id = 3, Sales = 400 },
};

sales.GroupBy(x=>x.Id, (Id, groupedSales) => new {Id,
SumOfSales = groupedSales.Sum( element => element.Sales) // Sales は合計する
})
.ToList().ForEach(Console.WriteLine);


(LINQ ではありませんが、 List.LookUp を使って実現することもできるようです → コメント:2014/03/22 00:29


Java

// javafx に Pair があったので Tuple 代わりに使っちゃった

List<Pair<Integer, Integer>> list1 = Arrays.asList(
new Pair<>(1, 100),
new Pair<>(2, 200),
new Pair<>(2, 300),
new Pair<>(3, 400)
);

list1.stream().collect(Collectors.groupingBy(x -> x.getKey()))
.entrySet().stream() // group化の結果が Map なので、エントリを Stream 化
.map(x -> new Pair<Integer, Integer>(
x.getKey(), // Key が Id に相当
x.getValue().stream().collect(Collectors.summingInt(y->y.getValue())))) // Value が List なのでまた Stream 化して合計を得る
.forEach(System.out::println);

// Collectors.groupingBy 使わずに Map.merge を使ったほうが分かりやすい気も。。。
list1.stream().collect(
() -> new HashMap<Integer, Integer>(),
(map, item) -> map.merge(item.getKey(), item.getValue(), (x, y) -> x + y), // 同じキーの値を加算してく
(left, right) -> left.putAll(right))
.forEach((k, v) -> System.out.println(k + ":" + v));


Java の方、カオスすぎる…。.NET の IGrouping を Map でやってるからだな。


Kotlin

val list1 = arrayOf(

Pair(1, 100),
Pair(2, 200),
Pair(2, 300),
Pair(3, 400));

list1.groupBy ({ x -> x.first }, { it.second }) // IDでグループ化して Map<Int, List<Int>> に
.map { Pair(it.key, it.value.sum()) } // List<Int> を合計して List<ID, 合計> に
.forEach { System.out.println(it) }


[.NET]

{ Id = 1, SumOfSales = 100 }
{ Id = 2, SumOfSales = 500 } // ID=2 の Sales が合計されている
{ Id = 3, SumOfSales = 400 }

[Java]
1=100
2=500
3=400

[Kotlin]
(1, 100)
(2, 500)
(3, 400)


合計(Sum)、最大(Max)、最小(Min)、平均(Average)、件数(Count)、先頭(First)、終端(Last)

集計いろいろ。


C#

var list1 = Enumerable.Range(0, 10);

Console.WriteLine("Sum={0}", list1.Sum());
Console.WriteLine("Max={0}", list1.Max());
Console.WriteLine("Min={0}", list1.Min());
Console.WriteLine("Count={0}", list1.Count());
Console.WriteLine("First={0}", list1.First());
Console.WriteLine("Last={0}", list1.Last());
Console.WriteLine("Average={0}", list1.Average());


Java

List<Integer> list1 = Arrays.asList(0,1,2,3,4,5,6,7,8,9);

IntSummaryStatistics stats = list1.stream().collect(Collectors.summarizingInt(x -> x)); // Max,Min,Count,Average が取得できる
System.out.println("Sum=" + stats.getSum());
System.out.println("Max=" + stats.getMax());
System.out.println("Min=" + stats.getMin());
System.out.println("Count=" + stats.getCount());
System.out.println("First=" + list1.stream().findFirst().orElse(-1)); // summarizing では取れない
System.out.println("Last=" + list1.stream().sorted((x,y) -> y-x).findFirst().orElse(-1)); // 微妙
System.out.println("Average=" + stats.getAverage());

System.out.println("Average=" + IntStream.range(0, 10).average()); // 型指定 Stream なら average, sum がある(結果は Option に包まれる)



Kotlin

val list1 = arrayOf(0,1,2,3,4,5,6,7,8,9)

System.out.println("Sum=${list1.sum()}")
System.out.println("Max=${list1.max()}")
System.out.println("Min=${list1.min()}")
System.out.println("Count=${list1.count()}")
System.out.println("First=${list1.firstOrNull()}")
System.out.println("Last=${list1.lastOrNull()}")
System.out.println("Average=${list1.average()}")

Sum=45

Max=9
Min=0
Count=10
First=0
Last=9
Average=4.5

…疲れた。。。