Jak odstranit duplicitní řádky uvnitř textového souboru?

Můj obrovský (až 2 GiB) textový soubor obsahuje přibližně 100 přesných duplikátů každého řádku v něm (v mém případě k ničemu, protože soubor je datová tabulka ve formátu CSV).

Potřebuji odstranit všechna opakování, zatímco (nejlépe, ale to může být obětováno pro výrazné zvýšení výkonu) při zachování původního pořadí sekvencí. Ve výsledku musí být každý řádek jedinečný. Pokud by existovalo 100 stejných řádků (obvykle se duplikáty šíří po celém souboru a nebyli by sousedi), měl by zbývat pouze jeden druh.

Napsal jsem program ve Scale (zvažte to Java, pokud nevíte o Scale), abyste to mohli implementovat. Ale možná existují rychlejší nativní nástroje napsané v C, které to dokážou rychleji?

UPDATE: řešení awk "!seen[$0]++" filename se mi zdálo v pořádku, pokud soubory byly blízko 2 GiB nebo menší, ale teď, když mám vyčistit soubor 8 GiB, už to nefunguje. Vypadá to, že na Macu se 4 GiB RAM a 64bitovým počítačem se systémem Windows 7 se 4 GiB RAM je nekonečno a 6 GiB swapu právě dochází paměť. A necítím se nadšený, že to zkusím na Linuxu se 4 GiB RAM vzhledem k této zkušenosti.

Komentáře

  • toto zničí vaše objednávání, ale zkusili jste sort -u, netuším, jak nebo jestli to může běžet na tak velkém souboru,
  • C často není výrazně rychlejší než Java, a pokud Nyní jej ‚ znovu spouštíte (v pořadí), existuje ‚ reálná šance ‚ ll dokončete, než zde dostanete odpověď, implementujte ji a dokončí běh; mimo provoz, sort -u bude pravděpodobně rychlejší.

Odpovědět

awk řešení zobrazené na #bash (Freenode):

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

komentáře

  • Právě jsem to zkusil na 2G souboru a na mém notebooku to trvalo tři minuty. Není špatné. Také jsem zkusil uniq název souboru | awk ‚! viděno [$ 0] ++ ‚, ale nebylo to ‚ žádné rychlejší.
  • @HashWizard: tento příkaz se netřídí, ale eliminuje každý další výskyt stejného řádku
  • Zajímá vás, jak tento příkaz funguje? – Viz zde: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams ano , funguje to, že jsou náhodně distribuovány.
  • zachovat nové řádky nebo řádky s mezerami awk '/^\s*?$/||!seen[$0]++'

odpověď

Existuje jednoduchá (což není samozřejmá) metoda využívající standardní nástroje, která kromě spuštění sort, který má ve většině implementací specifické optimalizace pro velké soubory (dobrý externí třídicí algoritmus). Výhodou této metody je, že se pouze smyčkuje přes všechny řádky uvnitř speciálních obslužných programů, nikdy ne uvnitř interpretovaných jazyků.

<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 

Pokud všechny řádky začínají znak bez mezer, můžete upustit od některých možností:

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

U velkého množství duplikací vyžaduje metoda, která vyžaduje pouze jednu kopii každý řádek v paměti bude fungovat lépe. S určitou režií tlumočení existuje velmi stručný awk skript (již zveřejněn enzotib ):

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

Méně výstižně: !seen[$0] {print} {seen[$0] += 1}, tj. vytiskněte aktuální řádek, pokud ještě nebyl viděn, a poté zvyšte seen čítač pro tento řádek (neinicializované proměnné nebo prvky pole mají číselnou hodnotu 0).

U dlouhých řádků můžete ušetřit paměť tím, že u každého řádku ponecháte pouze kontrolní součet, který není spoofable (např. kryptografický výtah) . Například pomocí SHA-1 potřebujete pouze 20 bajtů plus konstantní režii na řádek. Výpočet trávení je ale poměrně pomalý; tato metoda vyhraje pouze v případě, že máte rychlý procesor (zejména jeden s hardwarovým akcelerátorem pro výpočet trávení) a málo paměti vzhledem k velikosti souboru a dostatečně dlouhé řádky. Žádný základní nástroj vám neumožňuje vypočítat kontrolní součet pro každý řádek; musíte nést režii interpretace Perl / Python / Ruby /… nebo napsat specializovaný kompilovaný program.

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

Komentáře

  • @Gilles Na základě vašeho vysvětlení awk '!seen[$0]++' to znamená, že pokud awk uvidí 2 duplicitní řádky, ponechá vždy první a ignoruje všechny následující? (Nebo si ponechá poslední?)
  • @ user779159 Zachová první: každý vstupní řádek je buď vytištěn okamžitě (první výskyt), nebo vůbec (opakovaný výskyt).
  • Ale jak to ve srovnání s sort -u …?
  • @HashWizard A obyčajný sort -u změní pořadí.Moje odpověď ukazuje řešení, která zachovávají pořadí (přesněji pořadí prvních výskytů).
  • @Gilles byste řekl, že je rychlejší než sort -u pro velké soubory (10G) s 50% duplikáty ?

Odpověď

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

Všimněte si, že výstupní soubor bude být tříděny.

Komentáře

  • Ne tak rychle jako příkaz awk v jiných odpovědích, ale koncepčně jednoduché!
  • @Johann Dělám to docela často u souborů se stovkami tisíců (i miliónů) krátkých řetězců ukončených novým řádkem. Výsledky experimentů, které provádím, jsou velmi rychlé. Může být důležitější, pokud se použije ve skriptech, které se spouští znovu a znovu, úspora času může být značná.
  • Použijte sort -u k odstranění duplikátů během třídění, spíše než poté. (A šetří šířku pásma paměti) a přenáší ji do jiného programu). To je lepší než verze awk, pokud chcete také třídit výstup. (OP v této otázce chce, aby jeho původní objednávka byla zachována , takže je to dobrá odpověď pro trochu jiný případ použití.)
  • Trvalo to asi minutu, pro mě, pro 5,5 milionu řádkových souborů (celkem 1,8 GB). Brilantní.

Odpověď

Za předpokladu, že si můžete dovolit uchovat v paměti tolik jako duplikovaný soubor ( pokud jsou vaše data skutečně duplikována faktorem 100, to by mělo být asi 20MiB + režie), můžete to udělat velmi snadno pomocí Perlu.

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

Toto zachovává také pořadí.

Počet výskytů každého řádku můžete z hash %dup, pokud si to přejete, extrahovat jako bonus zdarma.

Pokud dáváte přednost awk, mělo by to být také (stejná logika jako verze Perl, stejné řazení, stejná data shromážděná v dup variable):

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

Komentáře

  • To je příliš dobré @Mat, I se chystal usrkávat soubor, lol ;-).
  • Nyní čekám na @ManAtWork na své sed a magické tkaní také 🙂
  • opět úžasné pro tip awk: – )
  • Je možné změnit perl skript pouze na remove Duplikujete sousední řádky?
  • @dumbledad: uniq dělá to samo o sobě

odpověď

Protože není k dispozici žádná jiná odpověď, je zde jedna:

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

Komentáře

  • Zachová se tím pořadí? Mimochodem, to pro mě nefungovalo. Moje verze je: GNU Awk 4.0.2
  • @Leonid ano, je. Vytiskne první výskyt jakéhokoli jedinečného řádku. Místní podpora byla poprvé představena ve verzi 4.1, která byla vydána v roce 2013.
  • Toto by měla být odpověď. Ve skutečnosti ‚ s odstraní duplikovaný řetězec v existujícím nebo aktuálním souboru, kde horní odpověď a většina odpovědí zde vytiskne pouze uniq / duplikované řetězce a nic neděláme a musíme vytvořit další výstup pro uložení výsledku.

Odpověď

Můžete použít uniq http://www.computerhope.com/unix/uuniq.htm

uniq hlásí nebo odfiltruje opakované řádky v souboru.

Komentáře

  • Při odpovědi je lepší dát nějaké vysvětlení, PROČ je vaše odpověď jediné. Jak se tedy tato odpověď liší od několika předchozích odpovědí?
  • Z manuálové stránky uniq: Poznámka: 'uniq' does not detect repeated lines unless they are adjacent. Takže ji musíte nejprve seřadit a uvolnit pořadí neduplicitních řádků.

Odpověď

Podložky Python One:

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

Komentáře

  • to způsobí, že se celý soubor usekne do paměti a nemusí být vhodný pro problém s OP ‚ s. Rovněž není zaručeno zachování pořadí
  • Děkuji za návrh, ‚ jsem se právě učil python .. zkusil jsem to jen pro účely učení ..:)
  • Zde ‚ s verze Pythonu 2.7, která není jednorázová, ale (stručně) vrací jedinečné řádky se zachováním pořadí bez načtení celého souboru do paměti nebo vytvoření jediného gigantického řetězce, který se bude podávat k tisku
  • Díky @ 1_CR Dnes se mám něco naučit 🙂 OrderedDict

Odpověď

Žádná z odpovědí zde na mém Macu nefungovala, takže jsem napsal jednoduchý python scénář, který pro mě funguje. Ignoruji přední / koncové mezery a také se nestarám o spotřebu paměti.

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) 

Výše uvedené uložte do jedinečného.py a běžte takto:

python unique.py inputfile.txt outputfile.txt 

Odpovědět

ŘEŠENÍ BEZ ÚDRŽBY ORIGINÁLNÍ SEKVENČNÍ OBJEDNÁVKY

Udělal jsem to pomocí následujícího kódu.

sort duplicates.txt | uniq > noDuplicates.txt 

Příkaz sort seřadí řádky abecedně a příkaz uniq odstraní duplikáty.

POZNÁMKA: Nejprve jsme řádky roztřídili proto, že uniq nezjistí duplicitní řádky, pokud spolu nesousedí.

Komentáře

  • Otázka požaduje metodu (nejlépe ) který udržuje pořadí zadávání; můžete upravit svou odpověď, abyste to vyřešili? Všimněte si, že existují existující odpovědi používající sort, které udržují pořadí zadávání, a jednu odpověď používající sort aniž by bylo nutné udržovat pořadí zadávání, ale efektivnějším způsobem než přesměrováním na uniq.
  • @StephenKitt Upraveno. Zkontroloval jsem další odpovědi, ale nemohl jsem ‚ nic najít pouze pomocí základních příkazů. Děkujeme za zpětnou vazbu.
  • Dal jsem vám odkaz na odpověď pouze se základními příkazy, ve skutečnosti pouze s jedním příkazem, sort -u (který je součástí POSIX ) ;-).
  • @StephenKitt Viděl jsem tuto odpověď. Můj je také způsob, jak problém vyřešit. Co chceš, abych udělal víc? Mám odpověď smazat?
  • Ne, neodstraňujte svou odpověď; Chtěl jsem se jen ujistit, že znáte další odpověď, protože jste řekl, že „nemůžete ‚ nic najít pouze pomocí základních příkazů“.

Odpověď

S bash 4, čistě bash řešení, které využívá asociativních polí lze použít. Zde je příklad

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 

Komentáře

  • Don ‚ t používají read smyčky ke zpracování velkých textových souborů. bash musí číst jeden bajt v čase, aby nedošlo k překročení nového řádku. Bash také obecně není velmi rychlý při zpracování textu ve srovnání s awk. Pokud toto použijete, read -ra se ve vašem vstupu vyhne zpětným lomítkům. Nezapomeňte také ‚ zapnout unset llist za smyčku, pokud ji vložíte do funkce shellu nebo používejte jej interaktivně.
  • @PeterCordes, nebo jste mohli jen odkazovat na toto 🙂

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *