Agent Grow Tech Notes
🤝

Javascript で簡単に group by ができるようになったってマジ?

2024/09/24に公開

はじめに

先日、フロントエンドの開発中に、配列をグルーピングする必要が出てきたので、サンプルコードを探すために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です。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/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です。
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/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]

https://learn.microsoft.com/ja-jp/dotnet/api/system.linq.enumerable.groupby

サンプルコードはこんな感じ。
コンソールログが出力しにくのはコンパイル言語のご愛嬌ですかね笑

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]
https://docs.oracle.com/javase/jp/8/docs/api/java/util/stream/Collectors.html#groupingBy-java.util.function.Function-
サンプルコードはこんな感じ。C#とあまり変わらないかな。

Drink.java
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;
    }
}
Main.java
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年経っていません。

  • 古いバージョンのブラウザに対応する必要がある
  • フロントエンドのビルド環境が古い

などのケースでは、使うことができません(悲しい!)

まとめ

登場したての記述ということもあって制約はあるものの、これまでの書き方に比べると圧倒的にわかりやすくてワクワクしました✨
使える環境ではドシドシ使っていきたいものですね。

脚注
  1. 毎回書き方を忘れるのは私だけでしょうか(苦笑) ↩︎

  2. 有用な使い方あったら教えて下さい! ↩︎

  3. alcoholは不要なので労力短縮のため省きました ↩︎

  4. .net framework3.5から実装されたはずなので、そこから換算(参考↩︎

  5. 筆者はわりとC#信者です ↩︎

  6. 筆者はわりとC#信者です(2回目) ↩︎

  7. 毎回忘れるのも許されそうなややこしさ ↩︎

Agent Grow Tech Notes
Agent Grow Tech Notes

Discussion