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
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ž verzeawk
, 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 nauniq
. - @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é ‚ zapnoutunset llist
za smyčku, pokud ji vložíte do funkce shellu nebo používejte jej interaktivně. - @PeterCordes, nebo jste mohli jen odkazovat na toto 🙂
sort -u
bude pravděpodobně rychlejší.