Hvordan fjernes duplikatlinjer inde i en tekstfil?

En enorm (op til 2 GiB) tekstfil af mig indeholder omkring 100 nøjagtige duplikater af hver linje i den (ubrugelig i mit tilfælde, da filen er en CSV-lignende datatabel).

Det, jeg har brug for, er at fjerne alle gentagelser, mens jeg (helst, men dette kan ofres for en betydelig ydeevne) opretholder den oprindelige rækkefølge. I resultatet skal hver linje være unik. Hvis der var 100 lige linjer (normalt er duplikaterne spredt over filen og ikke naboer), skal der kun være en af den slags tilbage.

Jeg har skrevet et program i Scala (overvej det Java, hvis du ikke kender Scala) til at implementere dette. Men måske er der hurtigere C-skrevne native-værktøjer, der kan gøre dette hurtigere?

UPDATE: awk "!seen[$0]++" filename -løsningen syntes at fungere fint for mig, så længe filerne var tæt på 2 GiB eller mindre, men nu som jeg skal rense en 8 GiB-fil, virker den ikke mere. Det ser ud til at tage uendeligt på en Mac med 4 GiB RAM og en 64-bit Windows 7 PC med 4 GiB RAM og 6 GiB swap løber bare tør for hukommelse. Og jeg er ikke begejstret for at prøve det på Linux med 4 GiB RAM i betragtning af denne oplevelse.

Kommentarer

  • dette ødelægger din bestilling, men har du prøvet sortering -u, har jeg ingen idé om, hvordan eller om den kan køre på en så massiv fil
  • C er ofte ikke væsentligt hurtigere end Java, og hvis du ‘ kører det (i rækkefølge) nu, der ‘ er en rimelig chance for, at det ‘ ll finish før du får et svar her, implementer det, og det kører færdigt; ude af rækkefølge vil sort -u sandsynligvis være hurtigere.

Svar

En awk løsning set på #bash (Freenode):

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

Kommentarer

  • Bare prøvet dette på en 2G-fil, og det tog tre minutter på min notesbog. Ikke dårligt. Jeg prøvede også uniq filnavn | awk ‘! set [$ 0] ++ ‘, men det var ikke ‘ t hurtigere.
  • @HashWizard: denne kommando sorterer ikke, men eliminerer hver næste forekomst af den samme linje
  • Undrer du dig over, hvordan denne kommando fungerer? – Se her: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams ja , det fungerer, hvis de er tilfældigt fordelt.
  • bevar nye linjer eller linjer med mellemrum awk '/^\s*?$/||!seen[$0]++'

Svar

Der er en enkel (hvilket ikke er åbenlyst) metode ved hjælp af standardværktøjer, der ikke kræver en stor hukommelse undtagen at køre sort, som i de fleste implementeringer har specifikke optimeringer til store filer (en god ekstern sorteringsalgoritme). En fordel ved denne metode er, at den kun løber over alle linjerne i specialværktøjer, aldrig inden for fortolkede sprog.

<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 

Hvis alle linjer begynder med en tegn, der ikke er mellemrum, kan du dispensere med nogle af mulighederne:

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

For en stor mængde duplikering er en metode, der kun kræver lagring af en enkelt kopi af hver linje i hukommelsen fungerer bedre. Med en vis fortolkning overhead er der et meget kortfattet awk-script til det (allerede indsendt af enzotib ):

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

Mindre kortfattet: !seen[$0] {print} {seen[$0] += 1}, dvs. udskriv den aktuelle linje, hvis den endnu ikke er set, og stig derefter seen tæller for denne linje (ikke-initialiserede variabler eller arrayelementer har den numeriske værdi 0).

For lange linjer kan du gemme hukommelse ved kun at holde et ikke-spoofable kontrolsum (f.eks. en kryptografisk fordøjelse) af hver linje . For eksempel ved brug af SHA-1 behøver du kun 20 byte plus en konstant overhead pr. Linje. Men databehandling er ret langsom; denne metode vinder kun, hvis du har en hurtig CPU (især en med en hardwareaccelerator til beregning af fordøjelserne) og ikke meget hukommelse i forhold til filens størrelse og tilstrækkelig lange linjer. Intet grundlæggende værktøj giver dig mulighed for at beregne et kontrolsum for hver linje; du bliver nødt til at bære fortolkningsomkostningerne for Perl / Python / Ruby /… eller skrive et dedikeret kompileret program.

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

Kommentarer

  • @Gilles Baseret på din forklaring af awk '!seen[$0]++', betyder det, at hvis awk ser 2 duplikatlinjer, vil det beholde den altid første og ignorere alle efterfølgende? (Eller beholder den sidste?)
  • @ user779159 Den beholder den første: hver inputlinje udskrives enten med det samme (første forekomst) eller slet ikke (gentagelse).
  • Men hvordan sammenligner det med sortering -u …?
  • @HashWizard En almindelig sort -u ændrer rækkefølgen.Mit svar viser løsninger, der bevarer rækkefølgen (rækkefølgen af de første hændelser, for at være præcis).
  • @ Gilles vil du sige, at det er hurtigere end sortering-u for store filer (10G) med 50% dubletter ?

Svar

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

Bemærk, at outputfilen vil sorteres.

Kommentarer

  • Ikke så hurtigt som awk kommandoen i andre svar, men konceptuelt simpelt!
  • @Johann Jeg gør det temmelig ofte på filer med hundreder af tusinder (endda millioner) korte strækninger, der er afsluttet med nye linjer. Jeg får resultaterne ret hurtigt til de eksperimenter, jeg laver. Det kan være vigtigere, hvis det bruges i scripts, der køres igen og igen, tidsbesparelser kan være betydelige.
  • Brug sort -u til at fjerne dubletter under sorteringen, snarere end efter. (Og gemmer hukommelsesbåndbredde) rør den til et andet program). Dette er kun bedre end awk versionen, hvis du også vil have din output sorteret. (OP på dette spørgsmål ønsker, at hans oprindelige ordre bevares , så dette er et godt svar til en lidt anden brugssag.)
  • Tog cirka et minut for mig for en 5,5 millioner linjefil (i alt 1,8 GB). Strålende.

Svar

Forudsat at du har råd til at beholde så meget som den af duplikerede fil i hukommelsen ( hvis dine data faktisk er duplikeret med en faktor 100, skal det være ca. 20MiB + overhead), kan du gøre dette meget let med Perl.

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

Dette bevarer ordren også.

Du kan udtrække antallet af forekomster af hver linje fra %dup hash, hvis du ønsker det, som en ekstra gratis bonus.

Hvis du foretrækker awk, skal dette også gøre det (samme logik som perl-versionen, samme rækkefølge, de samme data samlet i dup -variabel):

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

Kommentarer

  • Dette er for godt @Mat, jeg var ved at slurpe filen, lol ;-).
  • Venter nu på @ManAtWork for hans sed og awk magiske vævning også 🙂
  • fantastisk igen til awk tip: – )
  • Er det muligt at ændre perl-scriptet til kun at fjerne e duplikere tilstødende linjer?
  • @dumbledad: uniq gør det helt af sig selv

Svar

Da intet andet svar leveres på stedet, er der et:

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

Kommentarer

  • Bevarer dette ordren? Forresten fungerede dette ikke for mig. Min version er: GNU Awk 4.0.2
  • @Leonid ja, det gør det. Den udskriver den første forekomst af en unik linje. Inplace support blev først introduceret i version 4.1, som blev frigivet i 2013.
  • Dette burde være svaret. Det ‘ sletter faktisk den duplikerede streng i den eksisterende eller nuværende fil, hvor det øverste svar og de fleste af svarene her kun udskriver uniq / duplikerede strenge og ikke gør noget, og vi er nødt til at oprette en anden output for at gemme resultatet.

Svar

Du kan bruge uniq http://www.computerhope.com/unix/uuniq.htm

uniq rapporterer eller filtrerer gentagne linjer ud i en fil.

Kommentarer

  • Når du giver et svar, foretrækkes det at give en eller anden forklaring på HVORFOR dit svar er den ene. Så hvordan adskiller dette svar sig fra flere af de tidligere svar?
  • Fra uniq man-siden: Bemærk: 'uniq' does not detect repeated lines unless they are adjacent. Så du skal først sortere det og løse rækkefølgen af de ikke-duplikerede linjer.

Svar

Python One liners:

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

Kommentarer

  • dette får hele filen til at blive slurpet i hukommelsen og passer muligvis ikke til OP ‘ s problem. Også garanteret ikke at beholde orden
  • Tak for forslaget, jeg ‘ har lige lært python .. bare prøvet dette til læringsformål ..:)
  • Her ‘ s en Python 2.7-version, der ikke er en linie, men (kortfattet) returnerer unikke linjer, der bevarer orden uden enten at indlæse hele filen i hukommelsen eller oprette en enkelt gigantisk streng, der skal fødes til udskrivning
  • Tak @ 1_CR Jeg har noget at lære i dag 🙂 OrderedDict

Svar

Intet af svarene her fungerede for mig på min Mac, så jeg skrev en simpel python script, der fungerer for mig. Jeg ignorerer det ledende / efterfølgende mellemrum og er ligeglad med hukommelsesforbruget.

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) 

Gem ovenstående til unik.kør og kør sådan:

python unique.py inputfile.txt outputfile.txt 

Svar

LØSNING UDEN Opretholdelse af den oprindelige rækkefølge

Jeg gjorde det med følgende kodestykke.

sort duplicates.txt | uniq > noDuplicates.txt 

Kommandoen sort sorterer linjerne alfabetisk, og kommandoen uniq fjerner duplikaterne.

BEMÆRK: Hvorfor vi sorterede linjerne først er, at uniq registrerer ikke duplikatlinjer, medmindre de er ved siden af hinanden.

Kommentarer

  • Spørgsmålet beder om en metode (helst ) som opretholder inputrækkefølgen; kunne du redigere dit svar for at adressere det? Bemærk, at der er eksisterende svar ved hjælp af sort, som opretholder inputrækkefølgen, og et svar ved hjælp af sort uden at opretholde inputrækkefølge, men på en mere effektiv måde end at røre til uniq.
  • @StephenKitt Edited. Jeg inspicerede andre svar, men kunne ikke ‘ ikke finde noget kun med grundlæggende kommandoer. Tak for din feedback.
  • Jeg gav dig et link til et svar med kun grundlæggende kommandoer, faktisk kun en kommando, sort -u (som er en del af POSIX ) ;-).
  • @StephenKitt Jeg så svaret. Min er også en måde at håndtere problemet på. Hvad vil du have mig til at gøre mere? Skal jeg slette svaret?
  • Nej, slet ikke dit svar; Jeg ville bare sørge for, at du var opmærksom på det andet svar, da du sagde, at du “ikke kunne ‘ ikke finde noget kun med grundlæggende kommandoer”.

Svar

Med bash 4 er en ren-bash-løsning, der udnytter associerende arrays kan bruges. Her er et eksempel

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 brug read sløjfer til at behandle store tekstfiler. bash er nødt til at læse en-byte-ad-gangen for at undgå at overskride en ny linje. Bash er heller ikke særlig hurtig til tekstbehandling generelt sammenlignet med awk. Hvis du bruger dette, vil read -ra undgå at spise tilbageslag i dit input. Glem heller ikke ‘ unset llist efter sløjfen, hvis du sætter dette i en shell-funktion eller brug det interaktivt.
  • @PeterCordes, eller du kunne bare have henvist til dette 🙂

Skriv et svar

Din e-mailadresse vil ikke blive publiceret. Krævede felter er markeret med *