foo
bar
foo
foo
bar
bar
foo
みたいな中身のファイルがあったときに重複する行を削除して
foo
bar
だけにしたいです
Linux なら uniq コマンドが使えます
ただし uniq コマンドが除外するのは連続する重複する行なので今回みたいなケースではソートも行う必要があります
sort file.txt | uniq
でもこれってソートが無駄に思います
重複行を除去するだけなら一旦並び替えなくても 1 行ずつ読み取っていくだけでもできるはずです
あと 並びが替わってしまうので本来の並び順でほしい場合には使えません
特にちょうど今 重複除去したいデータはファイルは大きめですがほとんどが重複で最終的には 100 行にも満たないものです
1 行ずつ見ていくプログラムを作ったほうが速く処理できたりしそうです
ということで
import sys
if len(sys.argv) < 2:
exit()
filename = sys.argv[1]
with open(filename) as file:
set = set()
while line := file.readline():
line = line.rstrip("\n")
if line not in set:
set.add(line)
print(line)
というコードを用意しました
全件のソートという重そうな処理をスキップするわけですし スクリプト言語の Python ですがこっちのほうが速いのではないでしょうか
試してみます
ほとんどの行が重複するデータを作ります
values = ["a", "b", "c"]
values_len = len(values)
for i in range(10 * 1000 * 1000):
value = values[i % values_len]
print(value)
1000 万行で a,b,c のいずれかが入ってます
各行に改行文字も入るのでファイルサイズは 20MB になりました
それぞれの処理速度を比べてみます
[root@552d14e2dbe8 i]# python3 gen.py > data.txt
[root@552d14e2dbe8 i]# time python3 uniq.py data.txt
a
b
c
real 0m2.161s
user 0m2.160s
sys 0m0.000s
[root@552d14e2dbe8 i]# time ( sort data.txt | uniq )
a
b
c
real 0m1.242s
user 0m5.444s
sys 0m0.533s
ソートしたほうが速くなってますね……
user が real を超えているのでソートはマルチスレッドで処理しているのでしょうけど それにしても速すぎません?
数回実行してもだいたい同じような速度でした
1 行ずつ読み取って set に入ってるかチェックするだけなので Python でも十分な速度だと思いましたが 結構遅いのでしょうか?
set のハッシュ値計算や末尾の改行を除去する部分が遅め?
データ量を変えてもう 1 パターン試してみます
20MB より小さくしても 十分高速で速度を気にする必要もなさそうなので もっとサイズを大きくします
values = ["aaa", "bbb", "ccc"]
values_len = len(values)
for i in range(50 * 1000 * 1000):
value = values[i % values_len]
print(value)
5000 万行で 各行の文字数も増やしました
ファイルサイズは 191MB です
[root@198ac3e26189 opt]# time python3 uniq.py out.txt
aaa
bbb
ccc
real 0m12.368s
user 0m12.249s
sys 0m0.110s
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m8.984s
user 0m31.583s
sys 0m3.061s
これでもソートするほうが速いですね
ところで最初は遅めで何度か実行すると速くなりました
ブラウザと違って内部で情報を保持しないので 2 回目以降で劇的に速度が変わることはなさそうに思うのですけど
OS 側のファイルキャッシュ都合とかでしょうか
間で特別なことはせず単純に連続して実行した結果です
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m30.542s
user 1m16.845s
sys 0m13.606s
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m21.338s
user 0m46.432s
sys 0m15.657s
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m12.536s
user 0m32.160s
sys 0m7.133s
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m9.317s
user 0m30.287s
sys 0m3.861s
[root@198ac3e26189 opt]# time ( sort out.txt | uniq )
aaa
bbb
ccc
real 0m8.795s
user 0m31.538s
sys 0m2.983s
とりあえず ソートしても十分に速いのであまり気にしなくて良さそうです
並列処理前提なのでコアが 1 つしかない VM 環境とかだと話は変わってきそうですけど
また sort -u にすればソート時に重複する行を排除してくれて結果が同じになります
こっちのほうが速いかなと思いましたが 特に変わらないようでした
残る問題は並びが変わってしまうことです
uniq で得られる各行を元ファイルから探索して行数をもとに並び替えというのは面倒そうです
それに重複率が低い場合はほとんどの行を探索することになってここで時間がかかりそうです
コマンドのオプションを見てもいい感じにできそうなものがなかったですが いい方法ないのでしょうか