Hur tar jag bort dubbla rader i en textfil?

En stor (upp till 2 GiB) textfil av mig innehåller cirka 100 exakta dubbletter av varje rad i den (meningslöst i mitt fall, eftersom filen är en CSV-liknande datatabell.

Vad jag behöver är att ta bort alla repetitioner medan (helst, men detta kan offras för en betydande prestationsförbättring) att behålla den ursprungliga sekvensordningen. I resultatet ska varje rad vara unik. Om det fanns 100 lika rader (vanligtvis är dubbletterna spridda över filen och det kommer inte att vara grannar) är det bara en av den typen kvar.

Jag har skrivit ett program i Scala (anser det Java om du inte vet om Scala) för att implementera detta. Men kanske finns det snabbare C-skrivna inbyggda verktyg som kan göra detta snabbare?

UPPDATERING: awk "!seen[$0]++" filename -lösningen verkade fungera bra för mig så länge filerna var nära 2 GiB eller mindre men nu när jag ska städa upp en 8 GiB-fil fungerar det inte längre. Det verkar ta oändlighet på en Mac med 4 GiB RAM och en 64-bitars Windows 7 PC med 4 GiB RAM och 6 GiB swap tar slut på minne. Och jag känner mig inte entusiastisk över att testa det på Linux med 4 GiB RAM med tanke på den här upplevelsen.

Kommentarer

  • detta kommer att förstöra din beställning men har du försökt sortera -u har jag ingen aning om hur eller om den kan köras på en så massiv fil
  • C är ofta inte betydligt snabbare än Java, och om du ’ kör den (i ordning) nu, där ’ är en rimlig chans att det ’ kommer att avslutas innan du får svar här, implementera det och det går att köra; utom ordning kommer sort -u förmodligen att vara snabbare.

Svar

En awk -lösning sett på #bash (Freenode):

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

Kommentarer

  • Prövade precis detta på en 2G-fil och det tog tre minuter på min bärbara dator. Inte dåligt. Jag försökte också uniq filnamn | awk ’! sett [$ 0] ++ ’, men det var inte ’ t snabbare.
  • @HashWizard: det här kommandot sorteras inte, men eliminerar varje nästa förekomst av samma rad
  • Undrar du hur det här kommandot fungerar? – Se här: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams ja , det fungerar är att de är slumpmässigt fördelade.
  • bevara nya rader eller rader med mellanslag awk '/^\s*?$/||!seen[$0]++'

Svar

Det finns en enkel (vilket inte är självklart) metod med standardverktyg som inte kräver ett stort minne förutom att köra sort, som i de flesta implementeringar har specifika optimeringar för stora filer (en bra extern sorteringsalgoritm). En fördel med denna metod är att den bara slingrar över alla rader i specialverktyg, aldrig inuti tolkade språk.

<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 

Om alla rader börjar med en icke-blankstegstecken kan du avstå från några av alternativen:

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

För en stor mängd duplicering, en metod som bara kräver lagring av en enda kopia av varje rad i minnet kommer att fungera bättre. Med viss tolkning över huvudet finns det ett mycket kortfattat awk-skript för det (redan postat av enzotib ):

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

Mindre kortfattat: !seen[$0] {print} {seen[$0] += 1}, dvs skriva ut den aktuella raden om den inte har setts ännu, öka sedan seen räknare för den här raden (oinitialiserade variabler eller arrayelement har det numeriska värdet 0).

För långa rader kan du spara minne genom att bara hålla en icke-falsk kontrollsumma (t.ex. en kryptografisk sammandragning) av varje rad . Om du till exempel använder SHA-1 behöver du bara 20 byte plus en konstant overhead per rad. Men datainsamling är ganska långsam; den här metoden vinner bara om du har en snabb CPU (särskilt en med en hårdvaruaccelerator för att beräkna smälten) och inte mycket minne i förhållande till filens storlek och tillräckligt långa rader. Inget grundläggande verktyg låter dig beräkna en kontrollsumma för varje rad; du måste bära tolkningen av Perl / Python / Ruby / … eller skriva ett dedikerat sammanställt program.

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

Kommentarer

  • @Gilles Baserat på din förklaring av awk '!seen[$0]++', betyder det att om awk ser två dubbla rader kommer det att behålla den alltid första och ignorera alla efterföljande? (Eller behåller den sista?)
  • @ user779159 Den behåller den första: varje inmatningsrad skrivs antingen ut omedelbart (första förekomst) eller inte alls (upprepad förekomst).
  • Men hur jämför det med sort -u …?
  • @HashWizard En vanlig sort -u ändrar ordningen.Mitt svar visar lösningar som bevarar ordningen (ordningen för de första händelserna, för att vara exakt).
  • @ Gilles skulle du säga att det är snabbare än sortering -u för stora filer (10G) med 50% dubbletter ?

Svar

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

Observera att utdatafilen kommer att sorteras.

Kommentarer

  • Inte lika snabbt som kommandot awk i andra svar, men konceptuellt enkelt!
  • @Johann Jag gör det ganska ofta på filer med hundratusentals (till och med miljoner) korta strängar som avslutats med nya rader. Jag får resultaten ganska snabbt för de experiment jag gör. Det kan vara viktigare om det används i skript som körs om och om igen, tidsbesparingar kan vara betydande.
  • Använd sort -u för att ta bort dubbletter under sorteringen, snarare än efter. (Och sparar minnesbandbredd) som rör det till ett annat program). Det här är bara bättre än awk -versionen om du också vill sortera utdata. (OP för den här frågan vill att hans ursprungliga beställning ska evaras , så det här är ett bra svar för ett lite annorlunda användningsfall.) en 5,5 miljoner radfil (totalt 1,8 GB). Lysande.

Svar

Förutsatt att du har råd att behålla lika mycket som den av duplicerade filen i minnet ( om dina data verkligen dupliceras med en faktor 100, det borde vara ungefär 20MiB + overhead), kan du göra det mycket enkelt med Perl.

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

Detta bevarar ordningen också.

Du kan extrahera antalet förekomster av varje rad från %dup hash om du så önskar, som en extra gratis bonus.

Om du föredrar awk, bör detta också göra det (samma logik som perlversionen, samma ordning, samma data som samlats in i dup -variabel):

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

Kommentarer

  • Det här är för bra @Mat, jag var på väg att slurpa filen, lol ;-).
  • Väntar nu på @ManAtWork för hans sed och awk magiska vävning också 🙂
  • fantastiskt igen för awk-tipset: – )
  • Är det möjligt att ändra perl-skriptet till att bara ta bort e duplicera intilliggande rader?
  • @dumbledad: uniq gör det helt av sig själv

Svar

Eftersom inget annat svar tillhandahålls support på plats, här är ett:

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

Kommentarer

  • Bevarar detta beställningen? Förresten, det här fungerade inte för mig. Min version är: GNU Awk 4.0.2
  • @Leonid ja, det gör det. Den skriver ut den första förekomsten av en unik linje. Inplace-supporten introducerades först i version 4.1, som släpptes 2013.
  • Detta borde vara svaret. Det ’ raderar faktiskt den duplicerade strängen i den befintliga eller nuvarande filen där det översta svaret och de flesta av svaren här bara skriver ut uniq / duplicerade strängar och gör ingenting och vi måste skapa en annan utgång för att lagra resultatet.

Svar

Du kan använda uniq http://www.computerhope.com/unix/uuniq.htm

uniq rapporterar eller filtrerar bort upprepade rader i en fil.

Kommentarer

  • När du svarar är det bättre att ge någon förklaring till varför ditt svar är det. Så, hur skiljer sig detta svar från flera av de tidigare svaren?
  • Från uniq man-sidan: Obs: 'uniq' does not detect repeated lines unless they are adjacent. Så du måste först sortera det och lösa ordningen på de icke dubbla raderna.

Svar

Python One-liners:

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

Kommentarer

  • detta gör att hela filen slurps i minnet och kanske inte passar bra för OP ’ -problemet. Inte garanterat att jag behåller ordningen
  • Tack för förslaget, jag ’ har bara lärt mig python .. försökte bara detta för inlärningssyfte ..:)
  • Här ’ s en Python 2.7-version som inte är en enfodrad men (kortfattad) returnerar unika rader som bevarar ordning utan att antingen ladda hela filen i minnet eller skapa en enda gigantisk sträng att mata ut för att skriva ut
  • Tack @ 1_CR Jag har något att lära idag 🙂 OrderedDict

Svar

Inget av svaren här fungerade för mig på min Mac så jag skrev en enkel python manus som fungerar för mig. Jag ignorerar ledande / efterföljande tomrum och bryr mig inte om minneskonsumtion.

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) 

Spara ovanstående till unikt.py och kör så här:

python unique.py inputfile.txt outputfile.txt 

Svar

LÖSNING UTAN UNDERHÅLLNING AV DEN ORIGINALA SEKVENSORDNINGEN

Jag gjorde det med följande kodbit.

sort duplicates.txt | uniq > noDuplicates.txt 

Kommandot sort sorterar raderna alfabetiskt och kommandot uniq tar bort dubbletterna.

OBS: Varför vi sorterade raderna först är att uniq upptäcker inte dubbla rader såvida de inte ligger intill varandra.

Kommentarer

  • Frågan frågar efter en metod (helst ) som bibehåller inmatningsordningen; kan du redigera ditt svar för att ta itu med det? Observera att det finns befintliga svar med sort som bibehåller inmatningsordningen och ett svar med sort utan att upprätthålla inmatningsordningen men på ett mer effektivt sätt än att leda till uniq.
  • @StephenKitt Edited. Jag inspekterade andra svar men kunde inte ’ bara hitta något med grundläggande kommandon. Tack för din feedback.
  • Jag gav dig en länk till ett svar med endast grundläggande kommandon, faktiskt bara ett kommando, sort -u (som är en del av POSIX ) ;-).
  • @StephenKitt Jag såg svaret. Gruvan är också ett sätt att hantera problemet. Vad vill du att jag ska göra mer? Ska jag radera svaret?
  • Nej, ta inte bort ditt svar. Jag ville bara se till att du var medveten om det andra svaret, med tanke på att du sa att du ”inte kunde ’ inte hitta någonting bara med grundläggande kommandon.

Svar

Med bash 4, en ren bash-lösning som utnyttjar associerande matriser kan användas. Här är ett exempel

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 

Kommentarer

  • Don ’ t använd read loopar för att bearbeta stora textfiler. bash måste läsa en-byte-på-en-gång för att undvika att överskrida en ny linje. Bash är inte heller mycket snabb vid textbehandling i allmänhet jämfört med awk. Om du använder detta kommer read -ra att undvika att ta tillbaka snedstreck i din inmatning. Glöm inte att ’ t glömma att unset llist efter slingan, om du lägger detta i en skalfunktion eller använd den interaktivt.
  • @PeterCordes, eller så kan du bara ha refererat till detta 🙂

Lämna ett svar

Din e-postadress kommer inte publiceras. Obligatoriska fält är märkta *