辞書の「取り出す順番」を10言語で20回ずつ試したら、5通りの性格に分かれた ― その書き方、本当に通る?④

Go
B!

単語を数えて、はじめて出てきた順に出す。初心者向けの練習によくある問題です。各言語の「辞書」(連想配列・map)に数えて、そのまま回して出すプログラムを10言語で書き、同じ入力で 20回ずつ 動かしました。

$ wordcount b a b c a b

正解は、はじめて出てきた順の b: 3 → a: 2 → c: 1 です。

結果:5通りに分かれた

CODE Polyglot の入門 第10回 の正解表(6件)で、毎朝の検証と同じ Linux の環境で動かした結果です。

言語(辞書)6件中の正解(20回)上の入力の出方(20回)
Python dict・Ruby Hash・PHP 連想配列・Kotlin mutableMapOf毎回6件毎回 b → a → c
C# Dictionary毎回6件毎回 b → a → c
JavaScript オブジェクト {}毎回5件毎回 b → a → c
Java HashMap毎回4件毎回 a → b → c
Go map6件が11回、5件が4回、4件が5回18回は正しく、2回は c → b → a
Rust HashMap2〜5件(6件は0回)6通りすべての並びが出た
Swift Dictionary2〜5件(6件は0回)6通りすべての並びが出た

5つの性格に分けられます。

1. 入れた順に出ると決まっている(Python・Ruby・PHP・Kotlin)。言語の決まりとして約束されている2. 決まっていないが、今回は入れた順に出た(C#)。Dictionary の順番は約束されていない。要素を消したあとに足すと崩れることがある3. 数字に見えるキーだけ先に並ぶ(JavaScript)。2 10 2 1 を数えると 1: 1 → 2: 2 → 10: 1 になった4. 決まっていないが、毎回同じ順(Java)。毎回同じなので、手元の1回では気づきにくい5. 動かすたびに変わる(Go・Rust・Swift)。わざと毎回変わるように作られている

いちばん危ないのは Go

Go は、20回のうち11回は全件正解でした。手元で1回動かして「通った」と思ってしまう確率が半分以上あります。Rust と Swift は20回とも6件そろわなかったので、すぐに気づけます。

どう直したか

出てきた順を、辞書とは別の並び(リスト・配列)に記録して、その順に取り出します。Go ならこうです。

counts := map[string]int{}
order := []string{} // はじめて出てきた順
for _, w := range words {
	if _, ok := counts[w]; !ok {
		order = append(order, w)
	}
	counts[w]++
}
for _, w := range order {
	fmt.Printf("%s: %d\n", w, counts[w])
}

Java・Kotlin なら入れた順を保つ LinkedHashMap、JavaScript ならオブジェクトの代わりに Map を使う方法もあります。

まとめ

  • 「今回は入れた順に出た」と「入れた順に出ると決まっている」は別のこと
  • 順番が大事な処理は、言語の決まりを確かめてから辞書を選ぶ。分からなければ、順番を別に記録する
  • 1回のテストで通っても安心できない。この記事の数字は20回ずつ動かした結果です
  • 10言語の全プログラムは 入門 第10回 にあります
B!
← 一覧へ戻る