Bir metin dosyasının içindeki yinelenen satırlar nasıl kaldırılır?


126

Çok büyük (en fazla 2 GiB) metin dosyası, içindeki her satırın yaklaşık 100 tam kopyasını içeriyor (benim durumumda, dosya CSV benzeri bir veri tablosu olduğu için işe yaramaz).

İhtiyacım olan, orijinal sıra sırasını koruyarak (tercihen, ancak önemli bir performans artışı için feda edilebilir) tüm tekrarları kaldırmak. Sonuçta her satır benzersiz olmaktır. 100 eşit satır olsaydı (genellikle kopyalar dosyaya yayılır ve komşular olmaz), geriye kalan türden yalnızca biri kalır.

Bunu uygulamak için Scala'da bir program yazdım (Scala'yı bilmiyorsanız Java düşünün). Ama belki daha hızlı yapmak için daha hızlı C-yazılı yerel araçlar vardır?

GÜNCELLEME: awk '!seen[$0]++' filenamedosyalar 2 GiB'ye yakın veya daha küçük olduğu sürece çözüm benim için gayet iyi görünüyordu ancak şimdi bir 8 GiB dosyasını temizleyeceğim için artık çalışmıyor. 4 GiB RAM ve 4 GiB RAM ve 6 GiB takas kullanan 64 bit Windows 7 PC Mac'te sonsuzluk alıyor gibi görünüyor. Ve bu deneyimi verilen 4 GiB RAM ile Linux üzerinde denemek için hevesli hissetmiyorum.


bu, siparişinizi
mahveder

5
C genellikle Java'dan önemli ölçüde daha hızlı değildir ve şimdi çalıştırıyorsanız (sırayla), burada bir cevap almadan, uygulamayı uygulayarak ve çalışmayı bitirmeden önce bitmesi ihtimali oldukça yüksektir; sıra dışı, sort -umuhtemelen daha hızlı olacak.
Kevin

Yanıtlar:


215

awk#Bash'ta görülen bir çözüm (Freenode):

awk '!seen[$0]++' filename

1
Bunu bir 2G dosyada denedim ve defterimde üç dakika sürdü. Fena değil. Ayrıca uniq dosya adını da denedim | awk '! görüldü [$ 0] ++', ancak daha hızlı değildi.
mgjk,

Bu, awk2 dizi araması kullanan (Gilles cevaplarında genişletilmiş bir açıklama olarak gösterildi) kullanılan daha ayrıntılı bir sürümden şaşırtıcı derecede daha hızlıdır : 0m36.132s vs 0m49.958s .. 50 milyon satır için .. Darboğazın I / O olacağını düşündüm ama ekstra dizi araması ... dizideki 1 milyon element oldukça önemli bir
engel oluşturuyor

Ama bu nasıl -u .... ile karşılaştırılır?
HashWizard 13:17

1
@HashWizard: bu komut sıralama yapmaz ancak aynı satırın bir sonraki
tekrarını

1
@MaxWilliams evet, işe yarıyorlar, rastgele dağıtılıyorlar.
setholopolus

47

Çalıştırmak dışında büyük bir bellek gerektirmeyen standart uygulamaları kullanan basit bir yöntem (açıkça söylenmeyen) yöntemi sortvardır; çoğu uygulamada büyük dosyalar için belirli optimizasyonlar vardır (iyi bir harici sıralama algoritması). Bu yöntemin bir avantajı, yalnızca asla yorumlanmış dillerin içinde olmayan, özel amaçlı yardımcı programların içindeki tüm hatların üzerinden geçmesidir.

<input nl -b a -s : |           # number the lines
sort -t : -k 2 -u |             # sort and uniquify ignoring the line numbers
sort -t : -k 1n |               # sort according to the line numbers
cut -d : -f 2- >output          # remove the line numbers

Tüm satırlar boşluksuz bir karakterle başlıyorsa, bazı seçeneklerden vazgeçebilirsiniz:

<input nl | sort -k 2 -u | sort -k 1n | cut -f 2- >output

Büyük miktarda çoğaltma için, her satırın yalnızca tek bir kopyasının belleğe kaydedilmesini gerektiren bir yöntem daha iyi performans gösterir. Bazı yorumlar ek yüküyle, bunun için çok özlü bir awk senaryosu var (zaten enzotib tarafından gönderildi ):

<input awk '!seen[$0]++'

Daha az net bir şekilde: !seen[$0] {print} {seen[$0] += 1}yani henüz görülmediyse mevcut satırı yazdırın, sonra seenbu satırın sayacını artırın (başlatılmamış değişkenler veya dizi öğeleri sayısal değere sahiptir).

Uzun çizgiler için, her bir satırın yalnızca sahtekar olmayan bir sağlama toplamını (örneğin bir şifreleme özeti) tutarak hafızadan tasarruf edebilirsiniz. Örneğin, SHA-1'i kullanırken, yalnızca satır başına 20 bayt artı sabit bir ek yüke ihtiyacınız var. Ancak bilgi işlem özetleri oldukça yavaştır; bu yöntem yalnızca hızlı bir CPU'nuz varsa (özellikle özetleri hesaplamak için donanım hızlandırıcısına sahip bir tane) ve dosyanın boyutuna ve yeterince uzun satırlara göre çok fazla bellek yoksa kazanır. Hiçbir temel yardımcı program, her satır için bir sağlama toplamı hesaplamanıza izin vermez; tercümanlık Perl / Python / Ruby /… 'nin kontrolüne katlanmak veya özel bir derlenmiş program yazmak zorunda kalırsınız.

<input perl -MDigest::MD5 -ne '$seen{Digest::MD5::md5($_)}++ or print' >output

@Gilles Sizin açıklamanıza dayanarak, awk '!seen[$0]++'eğer awk 2 yinelenen satır görürse, her zaman birinciyi koruyacak ve izleyenleri görmezden gelecek mi demek oluyor? (Ya da sonuncusu kalmaya devam eder mi?)
user779159

1
@ user779159 Birincisini tutar: her giriş satırı hemen yazdırılır (ilk defa) veya hiç (her şeyi tekrar eder) yazdırılır.
Gilles

Ama bu nasıl -u ... ile karşılaştırılır?
HashWizard

@HashWizard Bir düz sort -usırasını değiştirir. Cevabım sırayı koruyan çözümler gösteriyor (ilk olayların sırası, kesin).
Gilles,

@Gilles,% 50 kopya ile büyük dosyalar (10G) için sort -u'dan daha hızlı olduğunu söyler misiniz?
HashWizard

25
sort -u big-csv-file.csv > duplicates-removed.csv

Çıktı dosyasının sıralanacağına dikkat edin.


1
awkDiğer cevaplardaki komut kadar hızlı değil , fakat kavramsal olarak basit!
Johann

@Johann Bu sık sık yüz binlerce (hatta milyonlarca) kısa yeni satır sonlandırılmış dizgisi olan dosyalarda yapıyorum. Yaptığım deneyler için sonuçları oldukça hızlı alıyorum. Tekrar tekrar çalıştırılan komut dosyalarında kullanılırsa daha önemli olabilir, zamandan tasarruf önemli olabilir.
Vladislavs Dovgalecs

1
sort -uSıralama sırasında değil, sıralama sırasında çoğaltmaları kaldırmak için kullanın . (Ve hafıza bant genişliğinden tasarruf sağlar) başka bir programa aktarılması). Bu, yalnızca awkçıktınızın sıralanmasını istiyorsanız sürümden daha iyidir . (Bu sorudaki OP orijinal siparişinin korunmasını istiyor , bu yüzden bu biraz farklı bir kullanım durumu için iyi bir cevap.)
Peter Cordes

5.5 milyon satırlık bir dosya için (toplamda 1.8 GB) bir dakika kadar sürdü. Parlak.
Max Williams

18

Yinelenen dosyayı bellekte tutmayı göze alabileceğinizi varsayalım (verileriniz gerçekten 100'lük bir faktörle çoğaltılmışsa, yaklaşık 20MiB + yükü olmalıdır), bunu Perl ile kolayca yapabilirsiniz.

$ perl -ne 'print unless $dup{$_}++;' input_file > output_file

Bu da emri korur.

İsterseniz %dupücretsiz bir bonus olarak, dilerseniz her bir satırın oluşum sayısını karma değerinden çıkarabilirsiniz .

İsterseniz awkbunu da yapmalısınız (perl sürümüyle aynı mantık, aynı sıralama, dupdeğişkende toplanan aynı veriler ):

$ awk '{if (++dup[$0] == 1) print $0;}' input_file > output_file

Bu çok iyi @ Mat, ben dosya slurp, lol ;-) üzereydi.
Nikhil Mulley

Şimdi @ ManAtWork'u onun sed ve awk sihirli dokumacılığı için de bekliyor :-)
Nikhil Mulley

awk ipucu için tekrar harika :-)
Nikhil Mulley

1
Perl betiğini sadece bitişik satırları kopyalamak için değiştirmek mümkün müdür?
dumbledad

2
@dumbledad: uniqyaptığı tüm tek başına
Mat

3

Yerinde destek sağlamayan başka bir cevap olmadığı için, bir cevap:

gawk -i inplace '!a[$0]++' file

Bu siparişi koruyor mu? Bu arada, bu benim için işe yaramadı. Versiyonum:GNU Awk 4.0.2
Leonid

1
@Leonid evet, öyle. Herhangi bir benzersiz çizginin ilk oluşumunu yazdırır. Yerinde destek ilk olarak 2013 yılında piyasaya sürülen 4.1 sürümünde tanıtıldı.
Jan Chren

3

Http://www.computerhope.com/unix/uuniq.htm adresini kullanabilirsiniz.uniq

uniq bir dosyadaki tekrarlanan satırları raporlar veya filtreler.


Bir cevap verirken, cevabın NEDEN olduğuna dair bir açıklama yapmak tercih edilir . Öyleyse, bu cevap önceki yanıtların birçoğundan nasıl farklıdır?
Stephen Rauch

1
Uniq man sayfasından: Not: 'uniq' does not detect repeated lines unless they are adjacent. İlk önce onu sıralamanız ve kopya olmayan satırların sırasını kaybetmeniz gerekir.
Vindolin

2

Python One gömlekleri:

python -c "import sys; lines = sys.stdin.readlines(); print ''.join(sorted(set(lines)))" < InputFile

bu, tüm dosyanın belleğe sıkıştırılmasına neden olur ve OP'nin sorununa uygun olmayabilir. Ayrıca düzeni korumak için garanti
Iruvar

Öneri için teşekkürler, sadece python öğreniyorum .. sadece öğrenme amacı için bunu denedim .. :)
Rahul Patil

İşte bir liner olmayan, ancak (özlü bir şekilde) bir Python 2.7 sürümü, tüm dosyayı belleğe yüklemeden veya yazdırmak için beslemek için tek bir büyük dize oluşturmadan benzersiz satırları koruma sırasını döndürür
iruvar 16:13

Thanks @ 1_CR Bugün bir şey öğrendim :)OrderedDict
Rahul Patil

0

Buradaki cevapların hiçbiri Mac'imde işe yaramadı, bu yüzden benim için çalışan basit bir python betiği yazdım. Önde gelen / sondaki boşlukları görmezden geliyorum ve ayrıca bellek tüketimini de umursamıyorum.

import sys

inputfile = sys.argv[1]
outputfile = sys.argv[2]

with open(inputfile) as f:
    content = f.readlines()

content = [x.strip() for x in content]

my_list = list(set(content))

with open(outputfile, 'w') as output:
    for item in my_list:
        output.write("%s\n" % item)

Yukarıdakileri unique.py dosyasına kaydedin ve şöyle çalıştırın:

python unique.py inputfile.txt outputfile.txt

-1

Bash 4 ile, ilişkisel dizilerden yararlanan saf-bash çözeltisi kullanılabilir. İşte bir örnek

unset llist; declare -A llist;
while read -r line; do
if [[ ${llist[$line]} ]]; then
  continue
else 
  printf '%s\n' "$line"
  llist[$line]="x"
fi
done < file.txt

2
readBüyük metin dosyalarını işlemek için döngüler kullanmayın . bash, yeni bir çizgiyi atlamaktan kaçınmak için birer birer birer okumak zorundadır. Bash ayrıca genel olarak metin işlemede awk ile karşılaştırıldığında çok hızlı değildir. Bunu kullanırsanız, read -ragirişinizde ters eğik çizgi yemekten kaçınınız. Ayrıca, bunu bir kabuk işlevine koyarsanız veya etkileşimli olarak kullanırsanız, döngüden unset llist sonra yapmayı unutmayın .
Peter Cordes

2
@PeterCordes, yoksa sadece bu başvuruda bulunabilirdi :-)
iruvar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.