{ }codelper
グループ化辞書の走査条件つき並べ替えP0108

アナグラムでまとめる

一行目に単語の数 n、二行目に英小文字の単語 n 個が空白一つずつで区切られて与えられる。
並べる順だけが違い、同じ文字を同じ個数だけ使った単語どうしをアナグラムという(eat、tea、ate)。
単語をアナグラムどうしでまとめよ。
同じ単語が何回も出たら、出た回数だけすべてそのグループに入れる。
グループごとに一行 "個数: 単語 単語 …" を出力する。
グループ内の単語は辞書順(先頭の文字から比べてアルファベットが前のほうが先、一方が他方の先頭部分なら短いほうが先)で書く。
グループは単語の多いものから出力する。
単語の数が同じなら、二つのグループの最初の単語(並べ替えたグループの先頭の単語)を辞書順で比べ、前になるグループを先に出力する。

入力
1 ≤ n ≤ 5000、各単語は英小文字 1〜10 文字
出力
グループの数だけの行。
各行に単語の数、コロン(:)、そして単語ごとに空白一つの後に単語
例 1 · stdin
6
eat tea tan ate nat bat
stdout
3: ate eat tea
2: nat tan
1: bat
例 2 · stdin
7
listen silent enlist google banana elgoog tinsel
stdout
4: enlist listen silent tinsel
2: elgoog google
1: banana
main.py
入力(stdin)

実行すると、これらの行が順に input()・標準入力に渡されます(実行中に入力を求めません)。

$ 準備完了

コードを入力して解くにはパソコンで開いてください。モバイルでは問題と解答を見られます。

ほかの言語では?言語別の解答を見る · 8 言語の解答

アナグラムは文字を並べ替えた文字列が同じです(eat、tea → aet)。
これを辞書のキーにしてまとめます。
落とし穴はキーの型です。
Python sorted(w) はリストなのでキーにすると TypeError になり、"".join(sorted(w)) で文字列にします。
JS で [...w].sort() の配列をそのまま Map のキーにすると、配列は同じオブジェクトのときだけ等しいので単語ごとにばらばらになります(.join("") を付けます)。
Java・C# も char[] をキーにすると中身ではなく配列オブジェクトで比べるので、new String(c)・new string(c) に変えます。
C++ の std::string は値なので、並べ替えたコピーがそのままキーになります。
Craykon は文字をリストに入れて 並べ替え()(韓国語 정렬())してから つなぐ("")(韓国語 잇기(""))でキーを作ります。
文字コードの合計をキーにすると ad と bc(97+100 = 98+99)が一つのグループになるのもよくある間違いです。
C には辞書がないので、(並べ替えた文字、単語)の順に qsort すれば同じグループが並んで出て、グループ内も辞書順になります。
グループを回る順は、Python dict・JS Map・Craykon キー一覧(韓国語 열쇠목록)は入れた順、Java HashMap・C# Dictionary は順を約束せず、C++ std::map はキー順です。
どれも出力順とは違うので「大きいグループが先、同じなら最初の単語」で並べ直す必要があります。
大きさだけで並べると、同じ大きさのグループが最初に出た順のまま残ります。

Pythonmain.py · 13 行
n = int(input())words = input().split() # 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")groups = {}for w in words:    key = "".join(sorted(w))      # sorted(w)는 목록이라 사전 열쇠가 될 수 없다    groups.setdefault(key, []).append(w) result = [sorted(g) for g in groups.values()]result.sort(key=lambda g: (-len(g), g[0]))   # 큰 묶음 먼저, 같으면 첫 낱말 사전 순for g in result:    print(f"{len(g)}: {' '.join(g)}")
JavaScriptmain.js · 14 行
const [n, ...words] = require("fs").readFileSync(0, "utf8").trim().split(/\s+/); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")const groups = new Map();for (const w of words) {  const key = [...w].sort().join(""); // 배열 그대로는 열쇠로 못 쓴다(배열은 같은 객체일 때만 같다)  if (!groups.has(key)) groups.set(key, []);  groups.get(key).push(w);} const result = [...groups.values()].map((g) => g.sort());const cmp = (a, b) => (a < b ? -1 : a > b ? 1 : 0);result.sort((a, b) => b.length - a.length || cmp(a[0], b[0])); // 큰 묶음 먼저, 같으면 첫 낱말console.log(result.map((g) => `${g.length}: ${g.join(" ")}`).join("\n"));
TypeScriptmain.ts · 16 行
import { readFileSync } from "node:fs";const [, ...words]: string[] = readFileSync(0, "utf8").trim().split(/\s+/); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")const groups = new Map<string, string[]>();for (const w of words) {  const key = [...w].sort().join(""); // 배열 그대로는 열쇠로 못 쓴다(배열은 같은 객체일 때만 같다)  const g = groups.get(key);  if (g) g.push(w);  else groups.set(key, [w]);} const result: string[][] = [...groups.values()].map((g) => g.sort());const cmp = (a: string, b: string): number => (a < b ? -1 : a > b ? 1 : 0);result.sort((a, b) => b.length - a.length || cmp(a[0], b[0])); // 큰 묶음 먼저, 같으면 첫 낱말console.log(result.map((g) => `${g.length}: ${g.join(" ")}`).join("\n"));
JavaMain.java · 30 行
import java.io.*;import java.util.*; public class Main {    public static void main(String[] args) throws IOException {        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));        br.readLine();        String[] words = br.readLine().trim().split(" ");         // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")        Map<String, List<String>> groups = new HashMap<>();        for (String w : words) {            char[] c = w.toCharArray();            Arrays.sort(c);            String key = new String(c); // char[] 그대로는 열쇠가 안 된다(배열은 내용이 아니라 객체로 비교)            groups.computeIfAbsent(key, k -> new ArrayList<>()).add(w);        }         List<List<String>> result = new ArrayList<>();        for (List<String> g : groups.values()) {            Collections.sort(g);            result.add(g);        }        result.sort(Comparator.<List<String>>comparingInt(g -> -g.size()).thenComparing(g -> g.get(0)));         StringBuilder out = new StringBuilder();        for (List<String> g : result) out.append(g.size()).append(": ").append(String.join(" ", g)).append('\n');        System.out.print(out);    }}
C#Main.cs · 34 行
using System;using System.Collections.Generic;using System.Linq; class Program{    static void Main()    {        Console.ReadLine();        string[] words = Console.ReadLine().Split(' ', StringSplitOptions.RemoveEmptyEntries);         // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")        var groups = new Dictionary<string, List<string>>();        foreach (string w in words)        {            char[] c = w.ToCharArray();            Array.Sort(c);            string key = new string(c); // char[] 그대로는 열쇠가 안 된다(배열은 객체로 비교)            if (!groups.TryGetValue(key, out var list))            {                list = new List<string>();                groups[key] = list;            }            list.Add(w);        }         // 문자열은 Ordinal로 비교한다(기본 비교는 문화권 규칙을 따른다)        var result = groups.Values            .Select(g => g.OrderBy(w => w, StringComparer.Ordinal).ToList())            .OrderByDescending(g => g.Count)            .ThenBy(g => g[0], StringComparer.Ordinal);        foreach (var g in result) Console.WriteLine($"{g.Count}: {string.Join(" ", g)}");    }}
C++main.cpp · 35 行
#include <algorithm>#include <iostream>#include <map>#include <string>#include <vector> int main() {    int n;    std::cin >> n;    // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")    std::map<std::string, std::vector<std::string>> groups;    for (int i = 0; i < n; i++) {        std::string w;        std::cin >> w;        std::string key = w;        std::sort(key.begin(), key.end()); // std::string은 값이라 정렬한 복사본이 곧 열쇠        groups[key].push_back(w);          // []가 없으면 빈 vector를 만들어 준다    }     std::vector<std::vector<std::string>> result;    for (auto& [key, g] : groups) {        std::sort(g.begin(), g.end());        result.push_back(g);    }    std::sort(result.begin(), result.end(), [](const auto& a, const auto& b) {        if (a.size() != b.size()) return a.size() > b.size(); // 큰 묶음 먼저        return a[0] < b[0];                                    // 같으면 첫 낱말 사전 순    });     for (const auto& g : result) {        std::cout << g.size() << ":";        for (const auto& w : g) std::cout << " " << w;        std::cout << "\n";    }}
Cmain.c · 55 行
#include <stdio.h>#include <stdlib.h>#include <string.h> /* C에는 사전이 없다: (정렬한 글자, 낱말) 차례로 정렬하면 같은 묶음이 붙어 나오고 묶음 안도 사전 순이 된다 */typedef struct {    char key[11];    char word[11];} Item; typedef struct {    int start, size; /* items[start] ~ items[start + size - 1] */} Group; static Item items[5000];static Group groups[5000]; static int cmp_char(const void *a, const void *b) { return *(const char *)a - *(const char *)b; } static int cmp_item(const void *a, const void *b) {    const Item *x = a, *y = b;    int c = strcmp(x->key, y->key);    return c != 0 ? c : strcmp(x->word, y->word);} static int cmp_group(const void *a, const void *b) {    const Group *x = a, *y = b;    if (x->size != y->size) return y->size - x->size;           /* 큰 묶음 먼저 */    return strcmp(items[x->start].word, items[y->start].word); /* 같으면 첫 낱말 */} int main(void) {    int n;    scanf("%d", &n);    for (int i = 0; i < n; i++) {        scanf("%10s", items[i].word);        strcpy(items[i].key, items[i].word);        qsort(items[i].key, strlen(items[i].key), 1, cmp_char);    }    qsort(items, n, sizeof(Item), cmp_item);     int g = 0;    for (int i = 0; i < n; i++) {        if (i == 0 || strcmp(items[i].key, items[i - 1].key) != 0) groups[g++] = (Group){i, 0};        groups[g - 1].size++;    }    qsort(groups, g, sizeof(Group), cmp_group);     for (int i = 0; i < g; i++) {        printf("%d:", groups[i].size);        for (int j = 0; j < groups[i].size; j++) printf(" %s", items[groups[i].start + j].word);        printf("\n");    }    return 0;}
Craykonmain.cray · 52 行
コンソール.行読み();words = コンソール.行読み().空白除去().分ける(); // 글자를 정렬한 문자열이 같으면 같은 묶음이다(eat, tea → "aet")groups = 辞書();それぞれ (w の中 words){    key = 글자정렬(w);    もし (!groups.含む(key))    {        groups[key] = [];    }    groups[key].追加(w);} keys = groups.キー一覧;それぞれ (key の中 keys){    groups[key].並べ替え();}keys.並べ替え(큰묶음먼저);それぞれ (key の中 keys){    g = groups[key];    コンソール.書く(g.個数 + ": " + g.つなぐ(" "));} // 낱말의 글자를 하나씩 목록에 담아 정렬한 뒤 다시 잇는다글자정렬(w){    c = [];    繰り返す (i = 0; i < w.長さ; i++)    {        c.追加(w[i]);    }    c.並べ替え();    返す c.つなぐ("");} // 큰 묶음 먼저, 크기가 같으면 첫 낱말이 사전 순으로 앞선 묶음 먼저큰묶음먼저(앞, 뒤){    もし (groups[앞].個数 != groups[뒤].個数)    {        返す groups[뒤].個数 - groups[앞].個数;    }    もし (groups[앞][0] < groups[뒤][0])    {        返す -1;    }    返す 1;}