Hoe verwijder ik dubbele regels in een tekstbestand?

Een enorm (tot 2 GiB) tekstbestand van mij bevat ongeveer 100 exacte duplicaten van elke regel erin (in mijn geval nutteloos, aangezien het bestand een CSV-achtige gegevenstabel).

Wat ik nodig heb is om alle herhalingen te verwijderen terwijl (bij voorkeur, maar dit kan worden opgeofferd voor een aanzienlijke prestatieverbetering) de oorspronkelijke volgorde behouden blijft. In het resultaat moet elke regel uniek zijn. Als er 100 gelijke regels waren (meestal zijn de duplicaten verspreid over het bestand en zijn ze geen buren), dan is er nog maar één van de soort over.

Ik heb een programma in Scala geschreven (beschouw het als Java als u Scala niet kent) om dit te implementeren. Maar misschien zijn er snellere C-geschreven native tools die dit sneller kunnen doen?

UPDATE: de awk "!seen[$0]++" filename oplossing leek prima te werken voor mij, zolang de bestanden waren bijna 2 GiB of kleiner, maar nu ik een 8 GiB-bestand moet opschonen, werkt het niet meer. Het lijkt oneindig te duren op een Mac met 4 GiB RAM en een 64-bits Windows 7-pc met 4 GiB RAM en 6 GiB swap heeft gewoon geen geheugen meer. En ik voel me niet enthousiast om het te proberen op Linux met 4 GiB RAM gezien deze ervaring.

Opmerkingen

  • dit zal je bestelling vernietigen, maar heb je sort geprobeerd -u, ik heb geen idee hoe en of het op zon enorm bestand kan draaien
  • C is vaak niet significant sneller dan Java, en of je ‘ voert het nu uit (in volgorde), er is ‘ een redelijke kans dat het ‘ ll eindigen voordat je hier een antwoord krijgt, implementeer het en het is voltooid; buiten gebruik, sort -u zal waarschijnlijk sneller zijn.

Antwoord

Een awk oplossing gezien op #bash (Freenode):

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

Reacties

  • Ik probeerde dit gewoon op een 2G-bestand en het duurde drie minuten op mijn notebook. Niet slecht. Ik heb ook de uniq bestandsnaam | geprobeerd awk ‘! gezien [$ 0] ++ ‘, maar het was niet ‘ niet sneller.
  • @HashWizard: dit commando sorteert niet, maar elimineert elke volgende keer dat dezelfde regel voorkomt.
  • Vraagt u zich af hoe dit commando werkt? – Zie hier: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams ja , het werkt als ze willekeurig worden verdeeld.
  • bewaar nieuwe regels of regels met spaties awk '/^\s*?$/||!seen[$0]++'

Antwoord

Er is “een simpele (dat wil niet zeggen voor de hand liggende) methode met behulp van standaard hulpprogrammas die geen groot geheugen nodig hebben, behalve om sort, dat in de meeste implementaties specifieke optimalisaties heeft voor enorme bestanden (een goed extern sorteeralgoritme). Een voordeel van deze methode is dat het alleen over alle regels in hulpprogrammas voor speciale doeleinden heen loopt, nooit in geïnterpreteerde talen.

<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 

Als alle regels beginnen met een niet-witruimtetekens, kunt u enkele van de opties achterwege laten:

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

Voor een grote hoeveelheid duplicatie, een methode waarbij slechts één kopie van elke regel in het geheugen zal beter presteren. Met enige interpretatie overhead, is er “een zeer beknopt awk script daarvoor (al geplaatst door enzotib ):

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

Minder beknopt: !seen[$0] {print} {seen[$0] += 1}, dwz print de huidige regel als deze nog “niet gezien is, verhoog dan de seen teller voor deze regel (niet-geïnitialiseerde variabelen of array-elementen hebben de numerieke waarde 0).

Voor lange regels kunt u geheugen besparen door alleen een niet-spoofbare checksum (bijv. een cryptografisch overzicht) van elke regel te bewaren . Als u bijvoorbeeld SHA-1 gebruikt, heeft u slechts 20 bytes plus een constante overhead per regel nodig. Maar het samenvatten van computers is nogal traag; deze methode zal alleen winnen als je een snelle CPU hebt (vooral een met een hardwareversneller om de digests te berekenen) en niet veel geheugen in verhouding tot de grootte van het bestand en voldoende lange regels. Met geen enkel basisprogramma kunt u een checksum voor elke regel berekenen; je “zou de interpretatiekosten van Perl / Python / Ruby / … moeten dragen of een speciaal gecompileerd programma moeten schrijven.

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

Reacties

  • @Gilles Betekent dit op basis van uw uitleg van awk '!seen[$0]++' dat als awk 2 dubbele regels ziet, het de altijd eerste zal houden en alle de volgende? (Of zal het de laatste behouden?)
  • @ user779159 Het behoudt de eerste: elke invoerregel wordt ofwel onmiddellijk afgedrukt (eerste keer) of helemaal niet (herhaling).
  • Maar hoe verhoudt dat zich tot sorteren -u …?
  • @HashWizard Een gewone sort -u verandert de volgorde.Mijn antwoord toont oplossingen die de volgorde behouden (de volgorde van de eerste exemplaren, om precies te zijn).
  • @Gilles zou je zeggen dat het sneller is dan sort -u voor grote bestanden (10G) met 50% duplicaten ?

Answer

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

Merk op dat het uitvoerbestand worden gesorteerd.

Reacties

  • Niet zo snel als het awk commando in andere antwoorden, maar conceptueel simpel!
  • @Johann Ik doe dit vrij vaak op bestanden met honderdduizenden (zelfs miljoen) korte op een nieuwe regel beëindigde strings. Ik krijg de resultaten vrij snel voor de experimenten die ik doe. Het kan belangrijker zijn als het wordt gebruikt in scripts die keer op keer worden uitgevoerd, de tijdsbesparing kan aanzienlijk zijn.
  • Gebruik sort -u om duplicaten te verwijderen tijdens het sorteren, eerder dan erna. (En bespaart geheugenbandbreedte) door het naar een ander programma te sturen). Dit is alleen beter dan de awk -versie als je je uitvoer ook gesorteerd wilt hebben. (Het OP voor deze vraag wil dat zijn oorspronkelijke ordening behouden , dus dit is een goed antwoord voor een iets ander gebruik.)
  • Het duurde ongeveer een minuut voor mij een bestand van 5,5 miljoen regels (in totaal 1,8 GB). Briljant.

Antwoord

Ervan uitgaande dat u het zich kunt veroorloven om zoveel als het gededupliceerde bestand in het geheugen te bewaren ( als uw gegevens inderdaad met een factor 100 worden gedupliceerd, dat zou ongeveer 20MiB + overhead moeten zijn), kunt u dit heel gemakkelijk doen met Perl.

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

Dit behoudt ook de volgorde.

Je zou het aantal keren dat elke regel voorkomt uit de %dup hash kunnen halen als je dat zou willen, als een extra gratis bonus.

Als u de voorkeur geeft aan awk, zou dit het ook moeten doen (dezelfde logica als de perl-versie, dezelfde volgorde, dezelfde gegevens verzameld in de dup variabele):

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

Reacties

  • Dit is te goed @Mat, ik stond op het punt het bestand te slurpen, lol ;-).
  • Nu wachtend op @ManAtWork voor zijn sed en awk magic weavery ook 🙂
  • weer geweldig voor de awk tip: – )
  • Is het mogelijk om het perl-script te wijzigen in alleen remove e dubbele aangrenzende regels?
  • @dumbledad: uniq doet dat helemaal zelf

Antwoord

Aangezien er geen ander antwoord ter plaatse ondersteuning bood, is hier er een:

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

Reacties

  • Behoudt dit de volgorde? Bij mij werkte dit trouwens niet. Mijn versie is: GNU Awk 4.0.2
  • @Leonid ja, dat doet het. Het drukt het eerste exemplaar van een unieke regel af. De inplace-ondersteuning werd voor het eerst geïntroduceerd in versie 4.1, die werd uitgebracht in 2013.
  • Dit zou het antwoord moeten zijn. Het ‘ verwijdert in feite de gedupliceerde string in het bestaande of huidige bestand waar het bovenste antwoord en de meeste antwoorden hier alleen de uniq / gedupliceerde strings afdrukken en niets doen en we moeten maken een andere uitvoer om het resultaat op te slaan.

Answer

Je kunt uniq http://www.computerhope.com/unix/uuniq.htm

uniq rapporteert of filtert herhaalde regels in een bestand.

Opmerkingen

  • Bij het geven van een antwoord verdient het de voorkeur enige uitleg waarom uw antwoord het is. Dus, hoe verschilt dit antwoord van verschillende van de vorige antwoorden?
  • Van de uniq man-pagina: Opmerking: 'uniq' does not detect repeated lines unless they are adjacent. Dus je moet het eerst sorteren en losmaken de volgorde van de niet-dubbele regels.

Antwoord

Python One-liners:

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

Opmerkingen

  • dit zorgt ervoor dat het hele bestand in het geheugen wordt geslurpt en past mogelijk niet goed bij het OP ‘ s probleem. Ook niet gegarandeerd om de volgorde te behouden.
  • Bedankt voor de suggestie, ik ‘ heb net Python geleerd .. heb dit gewoon geprobeerd voor leerdoeleinden .. 🙂
  • Hier ‘ s een Python 2.7-versie die geen oneliner is, maar (kort en bondig) retourneert unieke regels met behoud van de volgorde zonder ofwel het hele bestand in het geheugen te laden of een enkele gigantische string te creëren om af te drukken
  • Bedankt @ 1_CR Ik heb vandaag iets geleerd 🙂 OrderedDict

Answer

Geen van de antwoorden hier werkte voor mij op mijn Mac, dus ik schreef een eenvoudige python script dat voor mij werkt. Ik negeer de voorloop / volg witruimte en geef ook niet om geheugengebruik.

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) 

Sla het bovenstaande op in uniek.py en voer als volgt uit:

python unique.py inputfile.txt outputfile.txt 

Antwoord

OPLOSSING ZONDER DE ORIGINELE VOLGORDE TE BEHOUDEN

Ik deed het met het volgende codestuk.

sort duplicates.txt | uniq > noDuplicates.txt 

Het sort commando sorteert de regels alfabetisch, en het uniq commando verwijdert de duplicaten.

OPMERKING: Waarom we de regels eerst hebben gesorteerd, is dat uniq detecteert geen dubbele regels tenzij ze aangrenzend zijn.

Opmerkingen

  • De vraag vraagt om een methode (bij voorkeur ) die de invoervolgorde handhaaft; kunt u uw antwoord bewerken om dat aan te pakken? Merk op dat er bestaande antwoorden zijn met sort die de invoervolgorde behouden, en één antwoord met sort zonder de invoervolgorde te behouden, maar op een efficiëntere manier dan door te sluizen naar uniq.
  • @StephenKitt Edited. Ik heb andere antwoorden bekeken, maar kon ‘ niets vinden met alleen basiscommandos. Bedankt voor je feedback.
  • Ik heb je een link gegeven naar een antwoord met alleen basiscommandos, in feite maar één commando, sort -u (dat deel uitmaakt van POSIX ) ;-).
  • @StephenKitt Ik zag dat antwoord. De mijne is ook een manier om het probleem aan te pakken. Wat wil je dat ik nog meer doe? Moet ik het antwoord verwijderen?
  • Nee, verwijder je antwoord niet; Ik wilde er gewoon zeker van zijn dat je op de hoogte was van het andere antwoord, aangezien je zei dat je “niet ‘ niets kon vinden met alleen basiscommandos”.

Answer

Met bash 4, een pure bash-oplossing die profiteert van associatieve arrays kan worden gebruikt. Hier is een voorbeeld

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 

Reacties

  • Don ‘ t gebruik read loops om grote tekstbestanden te verwerken. bash moet één byte-per-keer lezen om te voorkomen dat een nieuwe regel wordt overschreden. Bash is ook niet erg snel in tekstverwerking in het algemeen in vergelijking met awk. Als je dit wel gebruikt, zal read -ra voorkomen dat je backslashes eet. Vergeet ook niet ‘ om unset llist na de lus te plaatsen, als je dit in een shell-functie plaatst of gebruik het interactief.
  • @PeterCordes, of je had net kunnen verwijzen naar dit 🙂

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *