重複を消したら順番が変わった、Swift は「が」を1つにまとめた ― よく使う書き方を10言語で⑤

C#
B!

選んだタグの一覧から、同じものの2回目以降を消して、初めて出た順に残す。よく書く処理です。集合(Set)に入れて戻す、よく見かける書き方を8言語で試しました。

3 1 3 2 1(c001)から重複を消すと、正解は 3 1 2 です。8言語中6言語が、1 2 3 などの違う順番で返しました。

何をしたか

CODE Polyglot の「よく使う書き方」 で、決まりを先に決めました(正解表9件)。

  • 2回目以降を消し、初めて出た順に残す
  • 大文字と小文字は別。1 と 01 も別
  • 文字は見た目ではなく、Unicode の文字の並びで比べる

よく見かける書き方は8言語で用意しました。Ruby と C# は、普通に書けば全件通ったので置いていません。Python の list(set(…)) と、Go の map をそのまま回す書き方は、実行するたびに順番が変わるので、毎日の検証に使えません。代わりに、並べ替えてから出す形にしました。

集合に入れると、順番が変わる

よく見かける書き方3 1 3 2 1 の答え
決まり3 1 2
Python sorted(set(…))・Go のキーを sort・Rust の sort() と dedup()1 2 3(文字の順)
Java new HashSet<>(…)・Kotlin toHashSet()1 2 3(ハッシュの順)
JavaScript オブジェクトのキーで覚える1 2 3(整数に見えるキーは小さい順に並ぶ)

東京 大阪 名古屋(c007)では、Java は 名古屋 東京 大阪、Kotlin は 大阪 名古屋 東京 と返しました。どちらも中身は同じ HashSet ですが、並びが違います。最初に用意する表の大きさが違うためと考えています。どちらにしても、ハッシュの順は「入れた順」ではありません。

「同じ」の決め方が違う

**Swift の String** は、見た目が同じなら同じとみなします。「が」1文字(U+304C)と、「か」に結合用の濁点(U+3099)を付けた2文字(c005)を、Set<String> は1つにまとめました。Mac でも Linux でも同じです。

**PHP の in_array** は、第3引数を付けないと == で比べます。そのため "1" と "01" と "1.0"(c004)を同じ値とみなし、1 だけを残しました。

どちらも、目的によっては正しい動きです。たとえば、入力の揺れをそろえたいなら Swift の比べ方の方が便利です。大事なのは、どちらの「同じ」が欲しいのかを先に決めることです。

どう書いたか

多くの言語に、入れた順を覚えたまま重複を消す道具があります。

言語書き方
Pythonlist(dict.fromkeys(items))
JavaScript[...new Set(items)]
Javanew LinkedHashSet<>(…)
Kotlindistinct()
C#Distinct()
Rubyuniq
PHParray_unique

Go と Rust は、見たかどうかを集合で覚え、出す順は元の並びで決めています。Swift は文字の並び(UTF-8 のバイト列)で比べるようにしました。

var seen = Set<[UInt8]>()
let out = items.filter { seen.insert(Array($0.utf8)).inserted }

10言語のコードは、どれも9件すべてで決まりどおりの答えを出しています。

まとめ

  • 集合に入れて戻すと、順番は「入れた順」ではなくなる。実行するたびに変わる言語もある
  • 「同じ」の決め方は言語で違う(Swift は正規化して比べ、PHP の in_array は == で比べる)
  • 順番を保つ道具(dict.fromkeys・LinkedHashSet・distinct など)を使う

10言語のコードと全9件の結果は CODE Polyglot「順番を保って重複を消す」 にあります。

B!
← 一覧へ戻る