Javascript で簡単に group by ができるようになったってマジ?
はじめに
先日、フロントエンドの開発中に、配列をグルーピングする必要が出てきたので、サンプルコードを探すためにJavascript groupby
で検索をかけていました。[1]
検索結果を見て驚いたことに、いつの間にかJavascript自体にgroup byが実装されていたのです!!
感動が薄れる前に、記事にさせていただきます🙌
対象読者
- 何かしらの言語で配列のgroup byを知っている(SQLは除く)
- Javascriptの基本的な言語知識がある
- Javascprit(Typescript)で手軽にgroup byができなくて悩ましい
これが Javascript の groupBy
まずは実際にJavascriptのgroupByを見ていきましょう。
JavascriptにはgroupByが2種類あるので、それぞれ紹介します。
Object.groupBy
まずは、Object.groupBy
です。
個人的には、こちらはあまり使用頻度は高くなさそうだなぁという印象です。
グルーピングしたあとも配列形式のまま使いたいケースのほうが多そうかなと[2]
サンプルコードでは、ドリンクの配列をアルコール有り無しで分けてみました。
const list = [
{ name: 'Coffee', alcohol: false, type: 'coffee' },
{ name: 'tea', alcohol: false, type: 'tea' },
{ name: 'red wine', alcohol: true, type: 'wine' },
{ name: 'Cafe au Lait', alcohol: false, type: 'coffee' },
{ name: 'grean tea', alcohol: false, type: 'tea' },
{ name: 'white wine', alcohol: true, type: 'wine' },
];
const alcoholGroup = Object.groupBy(
list,
({ alcohol }) => (alcohol ? 'alcohol' : 'non-alcohol'),
);
console.log(alcoholGroup);
/* 実行結果
{
"non-alcohol": [
{
"name": "Coffee",
"alcohol": false,
"type": "coffee"
},
{
"name": "tea",
"alcohol": false,
"type": "tea"
},
{
"name": "Cafe au Lait",
"alcohol": false,
"type": "coffee"
},
{
"name": "grean tea",
"alcohol": false,
"type": "tea"
}
],
"alcohol": [
{
"name": "red wine",
"alcohol": true,
"type": "wine"
},
{
"name": "white wine",
"alcohol": true,
"type": "wine"
}
]
}
*/
Map.groupBy
つづいて、Map.groupBy
です。
こちらはフロントエンドエンジニア待望の内容ではないでしょうか!
見たほうがわかりやすいので、早速サンプルコードを記載します。
const list = [
{ name: 'Coffee', alcohol: false, type: 'coffee' },
{ name: 'tea', alcohol: false, type: 'tea' },
{ name: 'red wine', alcohol: true, type: 'wine' },
{ name: 'Cafe au Lait', alcohol: false, type: 'coffee' },
{ name: 'grean tea', alcohol: false, type: 'tea' },
{ name: 'white wine', alcohol: true, type: 'wine' },
];
const group = Map.groupBy(list, ({ type }) => type);
console.log(group);
/* 実行結果
new Map([
[
"coffee",
[
{
"name": "Coffee",
"alcohol": false,
"type": "coffee"
},
{
"name": "Cafe au Lait",
"alcohol": false,
"type": "coffee"
}
]
],
[
"tea",
[
{
"name": "tea",
"alcohol": false,
"type": "tea"
},
{
"name": "grean tea",
"alcohol": false,
"type": "tea"
}
]
],
[
"wine",
[
{
"name": "red wine",
"alcohol": true,
"type": "wine"
},
{
"name": "white wine",
"alcohol": true,
"type": "wine"
}
]
]
])
*/
生成されるのがMap型なのでArray
と同じようにとはいきませんが、forEach
には対応しているので、group byをした後に、さらに加工をするのも簡単にできそうです。
他言語と比較してみよう
せっかくなので、私がすでに知っていたgroup byと比較してみたいと思います。
Map.groupBy
のサンプルコードと同様のことをそれぞれの言語で書いてみましょう。[3]
C#
私が初めてソースコードでgroup byを体験したC#。
15年以上も前に実装していた先駆者ですね。[4]
サンプルコードはこんな感じ。
コンソールログが出力しにくのはコンパイル言語のご愛嬌ですかね笑
using System.Linq;
using System.Collections.Generic;
public class Drink {
public string Name { get; set; }
public string Type { get; set; }
}
public class Index {
public static void Main() {
var list = new List<Drink>{
new Drink{ Name="Coffee", Type="coffee" },
new Drink{ Name="tea", Type="tea" },
new Drink{ Name="red wine", Type="wine" },
new Drink{ Name="Cafe au Lait", Type="coffee" },
new Drink{ Name="grean tea", Type="tea" },
new Drink{ Name="white wine",Type="wine" },
};
var group = list.GroupBy(
drink => drink.Type,
drink => drink
);
foreach(var typeGroup in group) {
System.Console.WriteLine(typeGroup.Key + "=> [");
foreach(var drink in typeGroup) {
System.Console.WriteLine(" " + drink.Name);
}
System.Console.WriteLine("]");
}
}
}
/* 実行結果
coffee=> [
Coffee
Cafe au Lait
]
tea=> [
tea
grean tea
]
wine=> [
red wine
white wine
]
*/
正直、ずいぶん前に実装されたのに洗練されすぎててビビりますね。さすがC#!!![5]
Java
C#に続くこと数年後。お次はJavaです。
私が目にしたのが、ちょうどJava8が出たての頃だったのですが「JavaがC#に追いついた!」という感想を持ったのを思い出します。[6]
サンプルコードはこんな感じ。C#とあまり変わらないかな。
public class Drink {
private String name;
private String type;
public Drink(String name, String type) {
this.name = name;
this.type = type;
}
public String getName() {
return this.name;
}
public String getType() {
return this.type;
}
}
import java.util.*;
import java.util.stream.*;
public class Main {
public static void main(String[] args) throws Exception {
List<Drink> list = Arrays.asList(
new Drink("Coffee", "coffee"),
new Drink("tea", "tea"),
new Drink("red wine", "wine"),
new Drink("Cafe au Lait", "coffee"),
new Drink("grean tea", "tea"),
new Drink("white wine","wine")
);
Map<String, List<Drink>> group
= list.stream().collect(Collectors.groupingBy(Drink::getType));
for (Map.Entry<String, List<Drink>> entry : group.entrySet()) {
System.out.println(entry.getKey() + " => [");
for (Drink drink : entry.getValue()) {
System.out.println(" " + drink.getName());
}
System.out.println("]");
}
}
}
/* 実行結果
tea => [
tea
grean tea
]
coffee => [
Coffee
Cafe au Lait
]
wine => [
red wine
white wine
]
*/
PHP
まだない!!!
今後の活躍に期待です笑
(おまけ)group byを使わない場合
これまでと同じように書くならこんな感じでしょうか。[7]
const list = [
{ name: 'Coffee', type: 'coffee' },
{ name: 'tea', type: 'tea' },
{ name: 'red wine', type: 'wine' },
{ name: 'Cafe au Lait', type: 'coffee' },
{ name: 'grean tea', type: 'tea' },
{ name: 'white wine', type: 'wine' },
];
const group = list.reduce((grouping, drink) => {
const key = drink.type;
if (!grouping.get(key)) {
grouping.set(key, []);
}
grouping.get(key).push(drink);
return grouping;
}, new Map());
console.log(group);
/* 実行結果
new Map([
[
"coffee",
[
{
"name": "Coffee",
"type": "coffee"
},
{
"name": "Cafe au Lait",
"type": "coffee"
}
]
],
[
"tea",
[
{
"name": "tea",
"type": "tea"
},
{
"name": "grean tea",
"type": "tea"
}
]
],
[
"wine",
[
{
"name": "red wine",
"type": "wine"
},
{
"name": "white wine",
"type": "wine"
}
]
]
])
*/
難点は新しすぎること
便利なgroupbyですが、少しばかり難点があるので、最後に少しだけ記載します。
難点とは、導入されたのが最近過ぎること。
執筆時点で、初めてブラウザに実装されてからようやく1年ほど。全ブラウザの最新版に反映されたタイミングからはまだ1年経っていません。
- 古いバージョンのブラウザに対応する必要がある
- フロントエンドのビルド環境が古い
などのケースでは、使うことができません(悲しい!)
まとめ
登場したての記述ということもあって制約はあるものの、これまでの書き方に比べると圧倒的にわかりやすくてワクワクしました✨
使える環境ではドシドシ使っていきたいものですね。
Discussion