Kuinka poistaa päällekkäiset rivit tekstitiedoston sisällä?

Valtava (enintään 2 Gt) tekstitiedostoni sisältää noin 100 tarkkaa kopiota jokaisesta rivistä (minun tapauksessani turhaa, koska tiedosto on CSV: n kaltainen tietotaulukko).

Tarvitsen on poistaa kaikki toistot samalla kun (mieluiten, mutta tämä voidaan uhrata merkittävän suorituskyvyn parantamiseksi) säilyttäen alkuperäinen järjestysjärjestys. Tuloksessa jokaisen rivin on oltava ainutlaatuinen. Jos yhtäläisiä rivejä oli 100 (yleensä kaksoiskappaleet levitetään tiedostoon ja ne eivät ole naapureita), jäljellä on vain yksi laatu.

Olen kirjoittanut ohjelman Scalassa (pidä sitä Java, jos et tiedä Scalasta) tämän toteuttamiseksi. Mutta ehkä on nopeampia C-kirjoitettuja natiivityökaluja, jotka pystyvät tekemään tämän nopeammin?

PÄIVITYS: ratkaisu awk "!seen[$0]++" filename näytti toimivan hienosti minulle niin kauan kuin tiedostot olivat lähellä 2 Gt tai pienempiä, mutta nyt kun puhdistan 8 Gt: n tiedoston, se ei toimi enää. Vaikuttaa siltä, että Macissa on 4 Gt RAM-muistia ja 64-bittisessä Windows 7 -tietokoneessa, jossa on 4 Gt RAM-muistia. ja 6 GiB -vaihdossa vain loppuu muisti. Enkä ole innostunut kokeilemasta sitä Linuxissa, jossa on 4 GiB RAM-muistia tämän kokemuksen vuoksi.

Kommentit

  • tämä tuhoaa tilauksesi, mutta jos olet kokeillut lajittelua -u, minulla ei ole aavistustakaan siitä, miten tai voisiko se toimia niin massiivisella tiedostolla
  • C ei usein ole merkittävästi nopeampi kuin Java, ja jos ’ suoritat sen uudelleen (järjestyksessä) nyt, siellä ’ on kohtuulliset mahdollisuudet ’ ll viimeistely ennen kuin saat vastauksen täältä, toteuta se ja se on suoritettu loppuun; epäkunnossa, sort -u on todennäköisesti nopeampi.

Vastaa

awk -ratkaisu, joka näkyy #bash (Freenode):

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

kommentit

  • Kokeilin juuri tätä 2G-tiedostossa, ja muistikirjani kesti kolme minuuttia. Ei paha. Yritin myös uniq-tiedostonimeä awk ’! seen [$ 0] ++ ’, mutta sitä ei ollut ’ nopeammin.
  • @HashWizard: tämä komento ei lajittele, mutta poistaa saman rivin jokaisen seuraavan esiintymisen.
  • Mietitkö kuinka tämä komento toimii? – Katso täältä: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams kyllä , se toimii, jos ne jakautuvat satunnaisesti.
  • säilytä uudet viivat tai välilyönnit awk '/^\s*?$/||!seen[$0]++'

Vastaa

Siellä on yksinkertainen (ei kuitenkaan selvä) menetelmä, joka käyttää tavallisia apuohjelmia, jotka eivät vaadi suurta muistia lukuun ottamatta suoritusta sort, jolla on useimmissa toteutuksissa erityisiä optimointeja valtaville tiedostoille (hyvä ulkoinen lajittelualgoritmi). Tämän menetelmän etuna on, että se silmukkaa vain kaikkien erityisapuohjelmien sisällä olevien viivojen yli, ei koskaan tulkittujen kielten sisällä.

<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 

Jos kaikki rivit alkavat ei-välilyönti, voit luopua joistakin vaihtoehdoista:

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

Suuren määrän päällekkäisyyksiä varten menetelmä, joka vaatii vain yhden kopion tallennuksesta kukin muistin rivi toimii paremmin. Jonkin verran tulkintaa ”, on olemassa hyvin ytimekäs awk-komentosarja ( lähettänyt enzotib ):

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

Vähemmän ytimekkäästi: !seen[$0] {print} {seen[$0] += 1}, eli tulosta nykyinen rivi, jos sitä ei ole vielä nähty, lisää sitten seen laskuri tälle riville (alustamattomilla muuttujilla tai taulukkoelementeillä on numeroarvo 0).

Pitkillä viivoilla voit säästää muistia pitämällä jokaisen rivin vain tarkistamaton tarkistussumma (esim. salaus) . Esimerkiksi SHA-1: tä käytettäessä tarvitset vain 20 tavua plus vakiona yleiskustannukset per linja. Ruuansulatuksen laskeminen on kuitenkin melko hidasta; tämä menetelmä voittaa vain, jos sinulla on nopea keskusyksikkö (varsinkin sellainen, jossa on laitteistokiihdytin sulkujen laskemiseksi) eikä paljon muistia suhteessa tiedoston kokoon ja riittävän pitkiin viivoihin. Mikään perusapuohjelma ei anna sinun laskea tarkistussummaa jokaiselle riville; joudut kantamaan Perl / Python / Ruby /…: n tulkintakustannukset tai kirjoittamaan erillisen käännetyn ohjelman.

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

Kommentit

  • @Gilles awk '!seen[$0]++' selityksesi perusteella tarkoittaako se sitä, että jos awk näkee kaksi päällekkäistä riviä, se pitää aina ensimmäisen ja jättää huomiotta kaikki seuraavia? (Tai säilyttääkö viimeisen?)
  • @ user779159 Se pitää ensimmäisen: jokainen syöttörivi joko tulostetaan välittömästi (ensimmäinen esiintyminen) tai ei ollenkaan (toistuva esiintyminen).
  • Mutta miten tämä vertaa lajitteluun -u …?
  • @HashWizard Tavallinen sort -u muuttaa järjestystä.Vastaukseni näyttää ratkaisuja, jotka säilyttävät järjestyksen (tarkemmin sanottuna ensimmäisten esiintymien järjestys).
  • @Gilles sanotko, että se on nopeampi kuin lajittelu -u suurille tiedostoille (10G), joissa on 50% kopioita ?

Vastaa

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

Huomaa, että lähtötiedosto lajitella.

Kommentit

  • Ei niin nopeasti kuin awk -komento muissa vastauksissa, mutta käsitteellisesti yksinkertainen!
  • @Johann Teen tämän melko usein tiedostoissa, joissa on satoja tuhansia (jopa miljoonia) lyhyitä uuden rivin päättyneitä merkkijonoja. Saan tulokset melko nopeasti tekemistäni kokeista. Se voi olla tärkeämpää, jos sitä käytetään yhä uudelleen suoritettavissa skripteissä, ajansäästö voi olla huomattavaa.
  • Poista kaksoiskappaleet lajittelun avulla käyttämällä sort -u. pikemminkin kuin sen jälkeen. (Ja säästää muistin kaistanleveyttä) ohjaamalla se toiseen ohjelmaan). Tämä on vain parempi kuin awk -versio, jos haluat myös tulosteesi lajitellun. (Tämän kysymyksen OP haluaa alkuperäisen tilauksensa säilyvän säilytettynä , joten tämä on hyvä vastaus hieman erilaiseen käyttötapaukseen.)
  • Kesti noin minuutti minulle 5,5 miljoonan rivitiedoston (yhteensä 1,8 Gt). Loistava.

Vastaa

Olettaen, että sinulla on varaa pitää yhtä paljon kuin kopioitu tiedosto muistissa ( Jos tietosi ovat todellakin päällekkäisiä kertoimella 100, sen pitäisi olla noin 20 Mt + yleiskustannukset), voit tehdä tämän erittäin helposti Perlillä.

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

Tämä säilyttää myös tilauksen.

Voit halutessasi poimia kunkin rivin esiintymämäärän %dup -räsitunnistuksesta lisäbonuksena.

Jos haluat mieluummin awk, myös tämän pitäisi tehdä se (sama logiikka kuin perl-versio, sama järjestys, samat tiedot, jotka on kerätty dup muuttuja):

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

kommentit

  • Tämä on liian hyvä @Mat, I aikoi ryöstää tiedostoa, lol ;-).
  • Odottaa nyt myös @ManAtWorkia hänen sed- ja awk-taikakudoksilleen 🙂
  • jälleen mahtava awk-kärjelle: – )
  • Voiko perl-komentosarjan muuttaa vain poistettavaksi? e kopioida vierekkäisiä viivoja?
  • @dumbledad: uniq tekee kaiken yksin

Vastaa

Koska muuta vastausta ei annettu paikan päällä, tässä on yksi:

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

kommentit

  • Säilyttääkö tämä järjestyksen? Muuten, tämä ei toiminut minulle. Oma versioni on: GNU Awk 4.0.2
  • @Leonid kyllä, kyllä. Se tulostaa minkä tahansa ainutlaatuisen viivan ensimmäisen esiintymisen. Paikallinen tuki otettiin ensimmäisen kerran käyttöön versiossa 4.1, joka julkaistiin vuonna 2013.
  • Tämän pitäisi olla vastaus. Se ’ poistaa oikeastaan olemassa olevan tai nykyisen tiedoston päällekkäisen merkkijonon, jossa ylin vastaus ja suurin osa vastauksista vain tulostaa uniq / päällekkäiset merkkijonot ja tekemättä mitään, ja meidän on luotava toinen lähtö tuloksen tallentamiseksi.

Vastaa

Voit käyttää uniq http://www.computerhope.com/unix/uuniq.htm

uniq raportoi tai suodattaa tiedostossa toistuvat rivit.

Kommentit

  • Vastausta annettaessa on suositeltavaa antaa selitys MIKSI vastauksesi on . Joten miten tämä vastaus eroaa useista edellisistä vastauksista?
  • uniq-man -sivulta: Huomaa: 'uniq' does not detect repeated lines unless they are adjacent. Joten sinun on ensin lajiteltava se ja irrotettava muiden kuin päällekkäisten rivien järjestys.

Vastaa

Python One -vuoret:

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

kommentit

  • tämä aiheuttaa koko tiedoston sekoittamisen muistiin, eikä se välttämättä sovi hyvin OP ’ -ongelmaan. Ei myöskään taata järjestyksen säilyttämistä
  • Kiitos ehdotuksesta, olen ’ oppinut vain pythonia … kokeilin tätä vain oppimistarkoituksiin ..:)
  • Tässä ’ on Python 2.7 -versio, joka ei ole yhden linjan, mutta (ytimekkäästi) palauttaa ainutlaatuiset rivit järjestyksen säilyttämättä lataamatta koko tiedostoa muistiin tai luomalla yhtä jättimäistä merkkijonoa syötettäväksi tulostettavaksi
  • Kiitos @ 1_CR Minulla on jotain opittavaa tänään 🙂 OrderedDict

vastaus

Mikään täällä olevista vastauksista ei toiminut minulle Mac-tietokoneellani, joten kirjoitin yksinkertaisen pythonin käsikirjoitus, joka toimii minulle. Jätän huomiotta tyhjät tyhjät tilat ja välitän myös muistin kulutuksesta.

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) 

Tallenna yllä oleva yksilölliseksi.py ja aja näin:

python unique.py inputfile.txt outputfile.txt 

Vastaa

RATKAISU ILMAN ALKUPERÄISEN JAKSOJÄRJESTELMÄN SÄILYTTÄMISTÄ

Tein sen seuraavalla koodilla.

sort duplicates.txt | uniq > noDuplicates.txt 

sort -komento lajittelee rivit aakkosjärjestyksessä ja komento uniq poistaa kaksoiskappaleet.

HUOMAUTUS: miksi lajittelimme viivat ensin, uniq ei tunnista päällekkäisiä viivoja, elleivät ne ole vierekkäisiä.

Kommentit

  • Kysymys pyytää menetelmää (mieluiten ), joka ylläpitää syöttöjärjestystä; voisitko muokata vastaustasi siihen vastaamiseksi? Huomaa, että on olemassa olemassa olevia vastauksia, jotka käyttävät sort -toimintoa ja jotka ylläpitävät syöttöjärjestystä, ja yksi vastaus käyttäen sort ylläpitämättä syöttöjärjestystä, mutta tehokkaammin kuin siirtämällä viesti uniq.
  • @StephenKitt Edited. Tarkastin muita vastauksia, mutta en voinut ’ löytää mitään vain peruskomennoilla. Kiitos palautteestasi.
  • Annoin sinulle linkin vastaukseen, joka sisältää vain peruskomennot, itse asiassa vain yhden komennon, sort -u (joka on osa POSIX ) ;-).
  • @StephenKitt näin vastauksen. Minun on myös tapa käsitellä ongelmaa. Mitä haluat minun tekevän enemmän? Pitäisikö minun poistaa vastaus?
  • Ei, älä poista vastausta; Halusin vain varmistaa, että olit tietoinen toisesta vastauksesta, koska sanoit, ettet “voinut ’ löytää mitään vain peruskomennoilla.

vastaus

Bash 4: llä puhdas-bash-ratkaisu, joka hyödyntää -yhdistelmäryhmät voidaan käyttää. Tässä on esimerkki

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 

kommentit

  • Don ’ t käytä read -silmukoita suurten tekstitiedostojen käsittelemiseen. bashin on luettava yksi tavu kerrallaan, jotta vältetään uuden rivin ylitys. Bash ei myöskään ole kovin nopea tekstinkäsittelyssä yleensä verrattuna awk: iin. Jos käytät tätä, read -ra välttää syöksymättä takaisinviivoja syötteessäsi. Älä myöskään unohda ’ unohtaa unset llist silmukan jälkeen , jos laitat tämän kuoritoimintoon tai käytä sitä vuorovaikutteisesti.
  • @PeterCordes, tai olet voinut vain viitata tähän 🙂

Vastaa

Sähköpostiosoitettasi ei julkaista. Pakolliset kentät on merkitty *