📔
C#プログラマーのためのJavaScriptチートシート(LINQ編)
はじめに
以下の記事の続編です。
この記事では、C#プログラマーのためがJavaScriptでコレクション操作をする時に、どう書いたら良いのかをまとめています。
JavaScriptでLINQのような操作ができるライブラリには、Linq.js, ix.js, remeda, lodashなどがありますが、ここではこれらのライブラリを使わずに、JavaScriptのネイティブ機能だけを使った方法を示します。
概要
C#(LINQ) | JavaScript | 説明 |
---|---|---|
Where(predicate) |
filter(predicate) |
条件に一致する要素をフィルタリング |
First/FirstOrDefault(predicate) |
find(predicate) |
条件に一致する最初の要素を取得 |
Last/LastOrDefault(predicate) |
reverse().find(predicate) |
条件に一致する最初の要素を取得 |
Skip(count) |
slice(count) |
最初の指定された数の要素をスキップ |
Take(count) |
slice(count) |
最初の指定された数の要素を取得 |
Any(predicate) |
some(predicate) |
条件に一致する要素が1つでも存在するか |
All(predicate) |
every(predicate) |
全ての要素が条件を満たすか |
Select(selector) |
map(selector) |
各要素を変換 |
OrderBy(keySelector) |
sort((a, b) => keySelector(a) - keySelector(b)) |
昇順で並べ替え |
OrderByDescending(keySelector) |
sort((a, b) => keySelector(b) - keySelector(a)) |
降順で並べ替え |
Count() |
array.length |
要素の数を取得 |
Sum(selector) |
reduce((sum, x) => sum + x, 0) |
要素の合計を計算 |
Average(selector) |
reduce((sum, x) => sum + x, 0) / array.length |
要素の平均を計算 |
Aggregate(func) |
reduce(func) |
集計操作を実行 |
GroupBy(keySelector) |
reduce((map, x) => { let key = keySelector(x); (map[key] = map[key] |
グループ化 |
Concat(sequence) |
concat(sequence) |
2つの配列を連結 |
SelectMany(selector) |
flatMap(selector) |
ネストされた配列を平坦化して変換 |
クエリの例
以下、良く利用すると思われるコードをC#とJavaScriptで示します。
フィルタリング: Where → filter
C#
var evenNumbers = numbers.Where(n => n % 2 == 0);
JavaScript
const evenNumbers = numbers.filter(n => n % 2 === 0);
変換: Select → map
C#
var squares = numbers.Select(n => n * n);
JavaScript
const squares = numbers.map(n => n * n);
検索: First, FirstOrDefault → find
C#
var firstEven = numbers.First(n => n % 2 == 0);
JavaScript
const firstEven = numbers.find(n => n % 2 === 0);
findメソッドは、テスト関数を満たす値がない場合は、 undefined を返します。そのため、FirstOrDefault相当のコードは以下のようになります。
const firstOrDefault = numbers.find(n => n > 10) || 0; // デフォルト値: 0
スキップと取得: Skip, Take → slice
C#
var skipTwo = numbers.Skip(2);
var takeTwo = numbers.Take(2);
JavaScript
const skipTwo = numbers.slice(2); // 2つスキップ
const takeTwo = numbers.slice(0, 2); // 最初の2つ取得
条件チェック: Any, All → some, every
C#
bool hasEven = numbers.Any(n => n % 2 == 0);
bool allPositive = numbers.All(n => n > 0);
JavaScript
const hasEven = numbers.some(n => n % 2 === 0); // 1つでも条件を満たすか
const allPositive = numbers.every(n => n > 0); // 全ての要素が条件を満たすか
並べ替え: OrderBy, OrderByDescending → sort
C#
var sorted = numbers.OrderBy(n => n);
var descending = numbers.OrderByDescending(n => n);
JavaScript
const sorted = numbers.slice().sort((a, b) => a - b); // 昇順
const descending = numbers.slice().sort((a, b) => b - a); // 降順
注意: sortは破壊的メソッドなので、コピー(slice())を作成するのが安全です。
集約: Aggregate → reduce
C#
var sum = numbers.Aggregate((acc, n) => acc + n);
JavaScript
const sum = numbers.reduce((acc, n) => acc + n, 0);
グループ化: GroupBy → reduce
JavaScriptにはGroupByに相当する標準メソッドがないため、reduceを使って実装します。
C#
var grouped = numbers.GroupBy(n => n % 2 == 0 ? "Even" : "Odd");
JavaScript
const numbers = [ 1,2,3,4,5,6,7,8,9,10 ];
const grouped = numbers.reduce((acc, n) => {
const key = n % 2 === 0 ? "Even" : "Odd";
if (!acc[key]) acc[key] = [];
acc[key].push(n);
return acc;
}, {});
console.log(grouped['Even']);
console.log(grouped['Odd']);
結果:
2,4,6,8,10
1,3,5,7,9
平坦化: SelectMany → flatMap
C#
var numbers = new List<List<int>>
{
new List<int> { 1, 2, 3 },
new List<int> { 4, 5, 6 }
};
var results = numbers.SelectMany(n => n);
Console.WriteLine(string.Join(", ", results));
JavaScript
const numbers = [
[ 1,2,3 ],
[ 4,5,6]
];
const results = numbers.flatMap(n => n);
console.log(results);
結果
1,2,3,4,5,6
C#
var people = new List<Person>
{
new Person { Name = "Alice", Hobbies = new List<string> { "reading", "traveling" } },
new Person { Name = "Bob", Hobbies = new List<string> { "cycling", "trekking" } }
};
var hobbies = people.SelectMany(person => person.Hobbies);
Console.WriteLine(string.Join(", ", hobbies));
JavaScript
const people = [
{ name: "Alice", hobbies: ["reading", "traveling"] },
{ name: "Bob", hobbies: ["cycling", "trekking"] },
];
const hobbies = people.flatMap(person => person.hobbies);
console.log(hobbies);
結果
reading,traveling,cycling,trekking
メソッドチェーンの例
C#
var results = people.Where(p => p.Age > 25)
.Select(p => new { p.Name, p.Age })
.OrderBy(p => p.Name);
JavaScript
const people = [
{ name: 'alice', age: 28 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 30 },
{ name: 'Diana', age: 22 }];
const results = people
.filter(p => p.age > 25) // フィルタリング
.map(p => ({ name: p.name, age: p.age })) // 変換
.sort((a, b) => a.name.localeCompare(b.name)); // 並べ替え
for (const p of results ) {
console.log(p.name, p.age);
}
localeCompareは言語を考慮した比較を行います。
結果
alice 28
Charlie 30
最後に
JavaScriptのネイティブな機能だけでも、それなりにLINQと同じようなことが可能だとわかりました。しかし、どうしてもC#のLINQと比べると見劣りしてしまいます。
LINQに慣れたプログラマーの場合は、ix.jsなどのライブラリの利用も検討したほうが良いかもしれません。
ix.js
Linq.js
remeda
lodash
Discussion
そろそろ Iterator Helper が stage 4 になったのだから そっちの説明とかあってもいいかも……?
※ Linq to Object は
IEnumerable<T>
ベース なので iterator の方が挙動的に近いの意です。とも思いましたが、ただ mdn/caniuse 見た感じは safari がまだ との
コメントありがとうございます。
おっしゃる通り、純粋に文法的に見れば、Linq to Objectとの比較は、Iterator の方が適切ですね。
Safariがまだ対応していないのが痛い...
Iteratorは使ったことがありませんでしたので、勉強したいと思います。