Cum se elimină liniile duplicate dintr-un fișier text?

Un fișier text imens (de până la 2 GiB) conține aproximativ 100 de duplicate exacte ale fiecărei linii din acesta (inutil în cazul meu, deoarece fișierul este un tabel de date asemănător cu CSV).

Ceea ce am nevoie este să elimin toate repetările în timp ce (de preferință, dar acest lucru poate fi sacrificat pentru o creștere semnificativă a performanței) menținând ordinea secvenței inițiale. În rezultat, fiecare linie trebuie să fie unică. Dacă au existat 100 de linii egale (de obicei, duplicatele sunt răspândite în fișier și nu vor fi vecine) trebuie să rămână doar unul de acest fel.

Am scris un program în Scala (ia în considerare Java dacă nu știți despre Scala) pentru a implementa acest lucru. Dar poate că există instrumente native C-scrise mai rapide care pot face acest lucru mai repede?

UPDATE: soluția awk "!seen[$0]++" filename părea să funcționeze foarte bine pentru mine atâta timp cât fișierele erau aproape de 2 GiB sau mai mici, dar acum, pentru a curăța un fișier de 8 GiB, nu mai funcționează. Se pare că are infinit pe un Mac cu 4 GiB RAM și un PC Windows 7 pe 64 de biți cu 4 GiB RAM și schimbul de 6 GiB nu mai are memorie. Și nu mă simt entuziasmat să îl încerc pe Linux cu 4 GB de memorie RAM, având în vedere această experiență.

Comentarii

  • acest lucru vă va distruge comanda, dar, dacă ați încercat să sortați -u, nu am idee cum sau dacă poate rula pe un fișier atât de masiv.
  • C nu este adesea semnificativ mai rapid decât Java și dacă ‘ îl rulați (în ordine) acum, există ‘ o șansă justă ‘ voi termina înainte de a obține un răspuns aici, implementați-l și se termină rularea; în afara comenzii, sort -u va fi probabil mai rapid.

Răspuns

O soluție awk văzută pe #bash (Freenode):

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

Comentarii

  • Tocmai am încercat acest lucru pe un fișier 2G și a durat trei minute pe notebook-ul meu. Nu-i rău. De asemenea, am încercat numele fișierului uniq | awk ‘! văzut [$ 0] ++ ‘, dar nu era ‘ mai rapid.
  • @HashWizard: această comandă nu sortează, ci elimină fiecare apariție următoare a aceleiași linii
  • Vă întrebați cum funcționează această comandă? – Vedeți aici: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams da , funcționează dacă sunt distribuite aleatoriu.
  • păstrează linii noi sau linii cu spații awk '/^\s*?$/||!seen[$0]++'

Răspuns

Există „o metodă simplă (ceea ce nu înseamnă evident) care folosește utilitare standard care nu necesită o memorie mare, cu excepția pentru a rula sort, care în majoritatea implementărilor are optimizări specifice pentru fișiere uriașe (un bun algoritm de sortare externă). Un avantaj al acestei metode constă în faptul că se desfășoară numai pe toate liniile din interiorul utilitarilor speciale, niciodată în limbile interpretate.

<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 

Dacă toate liniile încep cu un caracter non-white, puteți renunța la unele dintre opțiuni:

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

Pentru o cantitate mare de duplicare, o metodă care necesită doar stocarea unei singure copii a fiecare linie din memorie va funcționa mai bine. Cu o interpretare generală, există „un script awk foarte concis pentru asta (deja postat de enzotib ):

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

Mai puțin concis: !seen[$0] {print} {seen[$0] += 1}, adică tipăriți linia curentă dacă nu a fost încă văzută, apoi incrementați seen contor pentru această linie (variabilele neinițializate sau elementele matricei au valoarea numerică 0).

Pentru liniile lungi, puteți salva memorie păstrând doar o sumă de control care nu poate fi falsificată (de exemplu, un rezumat criptografic) pentru fiecare linie . De exemplu, folosind SHA-1, aveți nevoie doar de 20 de octeți plus o cheltuială constantă pe linie. Însă calculul rezumatelor este destul de lent; această metodă va câștiga numai dacă aveți un procesor rapid (în special unul cu un accelerator hardware pentru a calcula rezumatele) și nu o cantitate mare de memorie în raport cu dimensiunea fișierului și linii suficient de lungi. Niciun utilitar de bază nu vă permite să calculați o sumă de control pentru fiecare linie; ar trebui să suportați interpretarea generală a Perl / Python / Ruby / … sau să scrieți un program compilat dedicat.

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

Comentarii

  • @Gilles Pe baza explicației dvs. despre awk '!seen[$0]++', înseamnă că, dacă awk vede 2 linii duplicate, va păstra prima întotdeauna și va ignora toate cele ulterioare? (Sau va păstra ultima?)
  • @ user779159 Păstrează prima: fiecare linie de intrare este fie tipărită imediat (prima apariție), fie deloc (repetare apariție).
  • Dar cum se compară asta cu sortarea -u …?
  • @HashWizard Un simplu sort -u modifică ordinea.Răspunsul meu arată soluții care păstrează ordinea (ordinea primelor apariții, mai exact).
  • @Gilles ați spune că este mai rapid decât sortarea -u pentru fișierele mari (10G) cu 50% dubluri ?

Răspuns

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

Rețineți că fișierul de ieșire va fi sortate.

Comentarii

  • Nu la fel de rapidă ca comanda awk în alte răspunsuri, ci conceptual simplu!
  • @Johann Fac asta destul de des pe fișiere cu sute de mii (chiar și milioane) de șiruri terminate de linie nouă. Rezultatele obțin destul de repede pentru experimentele pe care le fac. Poate fi mai important dacă este utilizat în scripturi care sunt rulate din nou și din nou, economiile de timp pot fi considerabile.
  • Utilizați sort -u pentru a elimina duplicatele în timpul sortării, mai degrabă decât după. (Și economisește lățimea de bandă a memoriei) conectându-l la un alt program). Acest lucru este mai bun decât versiunea awk doar dacă doriți să sortați și ieșirea. (PO pentru această întrebare dorește ca ordinea sa originală să fie păstrată , deci acesta este un răspuns bun pentru un caz de utilizare ușor diferit.)
  • A durat aproximativ un minut, pentru mine, pentru un fișier de 5,5 milioane de linii (1,8 GB în total). Strălucitor.

Răspuns

Presupunând că vă puteți permite să păstrați la fel de mult ca fișierul de-duplicat în memorie ( dacă datele dvs. sunt într-adevăr duplicate cu un factor de 100, care ar trebui să fie de aproximativ 20MiB + overhead), puteți face acest lucru foarte ușor cu Perl.

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

păstrează și comanda.

Puteți extrage numărul de apariții al fiecărei linii din hash-ul %dup, dacă ați dorit, ca bonus gratuit adăugat.

Dacă preferați awk, aceasta ar trebui să o facă și ea (aceeași logică ca versiunea perl, aceeași ordonare, aceleași date colectate în dup variabilă):

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

Comentarii

  • Acest lucru este prea bun @Mat, eu a fost pe punctul de a arunca fișierul, lol ;-).
  • Acum așteaptă ca @ManAtWork să fie și pentru țesătura sa magică și awk 🙂
  • minunat din nou pentru sfatul awk: – )
  • Este posibil să schimbați scriptul perl pentru a elimina numai Duplicați liniile adiacente?
  • @dumbledad: uniq face asta de la sine

Răspundeți

Deoarece niciun alt răspuns nu a oferit asistență internă, iată unul:

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

Comentarii

  • Acest lucru păstrează ordinea? Apropo, acest lucru nu a funcționat pentru mine. Versiunea mea este: GNU Awk 4.0.2
  • @Leonid da, da. Tipărește prima apariție a oricărei linii unice. Suportul inplace a fost introdus pentru prima dată în versiunea 4.1, care a fost lansată în 2013.
  • Acesta ar trebui să fie răspunsul. ‘ șterge de fapt șirul duplicat din fișierul existent sau curent, unde răspunsul de sus și majoritatea răspunsurilor aici imprimă doar șirurile uniq / duplicate și nu fac nimic și trebuie să creăm o altă ieșire pentru a stoca rezultatul.

Răspuns

Puteți utiliza uniq http://www.computerhope.com/unix/uuniq.htm

uniq raportează sau filtrează liniile repetate dintr-un fișier.

Comentarii

  • Când dați un răspuns, este de preferat să dați unele explicații cu privire la DE CE este răspunsul dvs. . Deci, în ce fel diferă acest răspuns de mai multe dintre răspunsurile anterioare?
  • Din pagina de manual uniq: Notă: 'uniq' does not detect repeated lines unless they are adjacent. Deci trebuie mai întâi să îl sortați și să-l pierdeți ordinea liniilor care nu sunt duplicate.

Răspuns

Căptușeli Python One:

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

Comentarii

  • acest lucru face ca întregul fișier să fie introdus în memorie și este posibil să nu fie o potrivire bună pentru problema OP ‘. De asemenea, nu este garantat să păstrez ordinea.
  • Vă mulțumim pentru sugestie, ‘ tocmai am învățat python .. tocmai am încercat acest lucru în scop de învățare ..:)
  • Aici ‘ s o versiune Python 2.7 care nu este un singur liner, ci (succint) returnează liniile unice păstrând ordinea fără a încărca întregul fișier în memorie sau a crea un singur șir gigantic pentru a fi hrănit pentru a fi tipărit. div>

Răspuns

Niciunul dintre răspunsurile de aici nu a funcționat pentru mine pe Mac, așa că am scris un python simplu script care funcționează pentru mine. Ignor spațiul alb principal / final și, de asemenea, nu-mi pasă de consumul de memorie.

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) 

Salvați cele de mai sus în unic.py și rulați astfel:

python unique.py inputfile.txt outputfile.txt 

Răspuns

SOLUȚIE FĂRĂ ÎNTREȚINEREA ORDINII ORIGINALE DE SECVENȚĂ

Am făcut-o cu următoarea piesă de cod.

sort duplicates.txt | uniq > noDuplicates.txt 

Comanda sort sortează liniile alfabetic, iar comanda uniq elimină duplicatele.

NOTĂ: De ce am sortat mai întâi liniile este că uniq nu detectează liniile duplicat decât dacă sunt adiacente.

Comentarii

  • Întrebarea solicită o metodă (de preferință ) care menține ordinea de introducere; ai putea modifica răspunsul tău pentru a aborda acest lucru? Rețineți că există răspunsuri existente care utilizează sort care mențin ordinea de intrare și un răspuns utilizând sort fără a menține ordinea de intrare, dar într-un mod mai eficient decât canalizarea către uniq.
  • @StephenKitt Editat. Am inspectat alte răspunsuri, dar nu am putut ‘ să găsesc nimic doar cu comenzi de bază. Vă mulțumim pentru feedback.
  • Ți-am dat un link la un răspuns cu numai comenzi de bază, de fapt o singură comandă, sort -u (care face parte din POSIX ) ;-).
  • @StephenKitt Am văzut acest răspuns. Al meu este, de asemenea, o modalitate de a rezolva problema. Ce vrei să fac mai mult? Ar trebui să șterg răspunsul?
  • Nu, nu șterge răspunsul; Am vrut doar să mă asigur că ești la curent cu celălalt răspuns, având în vedere că ai spus că „nu poți ‘ nu găsi nimic decât cu comenzi de bază”.

Răspuns

Cu bash 4, o soluție pure-bash care profită de matrice asociative poate fi folosit. Iată un exemplu

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 

Comentarii

  • Nu ‘ t utilizați buclele read pentru a procesa fișiere text mari. bash trebuie să citească câte un octet la un moment dat pentru a evita depășirea unei linii noi. De asemenea, Bash nu este foarte rapid la procesarea textului în general, comparativ cu awk. Dacă utilizați acest lucru, read -ra va evita consumarea de bare oblice în introducere. De asemenea, nu ‘ nu uitați să unset llist după bucla, dacă puneți aceasta într-o funcție shell sau folosiți-l interactiv.
  • @PeterCordes sau altceva ar fi putut face referire la acest lucru 🙂

Lasă un răspuns

Adresa ta de email nu va fi publicată. Câmpurile obligatorii sunt marcate cu *