CODE Polyglot / よく使う書き方 / 03

順番を保って重複を消す

同じ値の2回目以降を消し、初めて出た順に残します。タグの一覧や、選んだ項目の一覧でよく使います。集合(Set)に入れて戻す書き方では、言語によって順番が変わったり、実行するたびに変わったりします。

画面にこう出れば正解

どの言語で書いても、同じ入力なら画面に出る文字は1文字も違わない、というのが正解の決め方です(例は Python で動かしたときのコマンドです)。

基本

$ python3 unique.py 3 1 3 2 1
3
1
2
3件(2件を消した)

並べ替えない(集合に入れると順番が変わる言語がある)

$ python3 unique.py banana apple cherry apple banana date
banana
apple
cherry
date
4件(2件を消した)

大文字と小文字は別

$ python3 unique.py a A a B b
a
A
B
b
4件(1件を消した)

数としては同じでも、文字としては別

$ python3 unique.py 1 01 1.0 1
1
01
1.0
3件(1件を消した)

濁点の付け方が違う「が」

$ python3 unique.py が が が
が
が
2件(1件を消した)

空の文字列

$ python3 unique.py "" x "" y
(空)
x
y
3件(1件を消した)

重複が無い

$ python3 unique.py 東京 大阪 名古屋
東京
大阪
名古屋
3件(0件を消した)

全部同じ

$ python3 unique.py 🍣 🍣 🍣
🍣
1件(2件を消した)

引数が無い

$ python3 unique.py
0件(0件を消した)

10言語のプログラム

どれもそのまま保存して動かせます。毎朝、上の正解の例をすべて実際に動かして確かめています。

unique.php8.4.26 で 9 / 9 件の出力が正解と一致

<?php
// 受け取った文字列から、2回目以降に出たものを消し、初めて出た順に出す
$items = array_slice($argv, 1);

// array_unique は初めて出たものを残し、既定(SORT_STRING)では文字列として比べるので "1" と "01" は別になる。
// in_array($x, $out) を第3引数なしで使うと「==」で比べ、"1" と "01" と "1.0" が同じ数とみなされる
$out = array_unique($items);
foreach ($out as $s) {
    echo ($s === '' ? '(空)' : $s) . "\n";
}
// 消した分の添字が飛ぶので、数は count で数える
echo count($out) . '件(' . (count($items) - count($out)) . "件を消した)\n";

動かし方(ファイルを保存したフォルダで)

$ php unique.php 3 1 3 2 1

よく見かける書き方で試すと

仕事でよく見かける書き方(標準の関数をそのまま使う形や、よく紹介される正規表現など)を、同じ正解表で動かした結果です。言語を出した答えごとにまとめ、赤い字はその言語が実際に出した間違った行です。

入力よく見かける書き方の答え(8言語)
基本3 1 3 2 1
  • Python・Go・JavaScript・Java・Rust・Kotlin1
    2
    3
  • PHP・Swift✓ 正解
並べ替えない(集合に入れると順番が変わる言語がある)banana apple cherry apple banana date
  • Python・Go・Rustapple
    banana
  • Java・Kotlindate
    apple
    cherry
  • PHP・JavaScript・Swift✓ 正解
大文字と小文字は別a A a B b
  • Python・Go・RustA
    B
    a
  • PHP・JavaScript・Java・Kotlin・Swift✓ 正解
数としては同じでも、文字としては別1 01 1.0 1
  • PHP1件(3件を消した)
  • Python・Go・Rust01
    1
  • JavaScript・Java・Kotlin・Swift✓ 正解
濁点の付け方が違う「が」が が が
  • Python・Go・Java・Rust・Kotlinが
    が
  • Swift1件(2件を消した)
  • PHP・JavaScript✓ 正解
空の文字列"" x "" y

✓8言語すべて正解

重複が無い東京 大阪 名古屋
  • Python・Go・Rust名古屋
    東京
  • Java名古屋
    東京
    大阪
  • Kotlin大阪
    名古屋
    東京
  • PHP・JavaScript・Swift✓ 正解
全部同じ🍣 🍣 🍣

✓8言語すべて正解

引数が無い

✓8言語すべて正解

この書き方の決まり

  • 同じ文字列の2回目以降を消し、初めて出た順に1行ずつ出す。最後に残った数と消した数を出す
  • 大文字と小文字は別のもの。1 と 01 も別のもの
  • 文字は、見た目ではなく Unicode の文字の並びで比べる(「が」1文字と「か」+濁点の2文字は別)
  • 空の文字列も1つの値として扱う

解説

順番を保つ道具を使う

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

言語書き方
Pythonlist(dict.fromkeys(items))(dict は入れた順を保つ)
JavaScript[...new Set(items)](Set は入れた順を保つ)
Javanew LinkedHashSet<>(…)
Kotlindistinct()
C#Distinct()
Rubyuniq
PHParray_unique(キーは元の位置のまま残り、添字が飛ぶ。0 から振り直すなら array_values)
Go・Rust・Swift見たかどうかを集合で覚え、出す順は元の並びで決める

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

よく見かける書き方起きること
Java new HashSet<>(…)・Kotlin toHashSet()ハッシュの順になる。同じ入力でも、Java と Kotlin で並びが違った(最初に取る大きさが違うため)
Python list(set(…))・Go の map を range で回す実行するたびに順番が変わる(ハッシュの種や回す順を、毎回わざと変えている)
Python sorted(set(…))・Go のキーを sort・Rust の sort() と dedup()決まった順にはなるが、元の順ではなく文字の番号の順
JavaScript のオブジェクトのキーで覚える整数に見えるキー("3" "1")だけ、小さい順に前へ並ぶ

「同じ」の決め方

  • PHP の in_array は、何も指定しないと == で比べるので、"1" と "01" と "1.0" を同じとみなします。第3引数に true を渡すと、型も中身も同じかで比べます
  • Swift の String の == は、正規化して比べます。「が」1文字と「か」+濁点(U+3099)が同じになり、Set<String> では1つに減ります。この書き方では、文字の並び(UTF-8 のバイト列)で比べています

どちらも、目的によっては正しい動きです。「見た目が同じなら同じ」にしたいなら、Swift のほうが便利です。この書き方の決まりは「文字の並びが同じなら同じ」です。