Jak usunąć zduplikowane linie w pliku tekstowym?

Ogromny (do 2 GiB) plik tekstowy zawiera około 100 dokładnych duplikatów każdego wiersza (bezużyteczne w moim przypadku, ponieważ plik jest tabela danych w stylu CSV).

To, czego potrzebuję, to usunąć wszystkie powtórzenia, zachowując (najlepiej, ale można to poświęcić, aby znacznie zwiększyć wydajność) zachowując oryginalną kolejność sekwencji. W rezultacie każda linia ma być niepowtarzalna. Gdyby było 100 równych wierszy (zwykle duplikaty są rozmieszczone w pliku i nie są sąsiadami), zostałby tylko jeden tego rodzaju.

Napisałem program w Scali (rozważ to Java, jeśli nie wiesz o Scali), aby to zaimplementować. Ale może istnieją szybsze natywne narzędzia napisane w C, które potrafią to zrobić szybciej?

AKTUALIZACJA: rozwiązanie awk "!seen[$0]++" filename wydawało mi się działać dobrze, o ile pliki były blisko 2 GiB lub mniejsze, ale teraz, gdy mam wyczyścić plik 8 GiB, już nie działa. Wydaje się, że zajmuje nieskończoność na komputerze Mac z 4 GiB pamięci RAM i 64-bitowym komputerze z systemem Windows 7 i 4 GiB pamięci RAM i wymiany 6 GiB po prostu zabraknie pamięci. I nie jestem entuzjastycznie nastawiony do wypróbowania go w systemie Linux z 4 GiB RAM, biorąc pod uwagę to doświadczenie.

Komentarze

  • to zniszczy twoją kolejność, ale czy próbowałeś sort -u, nie mam pojęcia, jak i czy może działać na tak ogromnym pliku
  • C często nie jest znacznie szybszy niż Java, a jeśli ' używasz go teraz (w kolejności), ' masz duże szanse ' ll zakończ, zanim otrzymasz odpowiedź, zaimplementuj ją i zakończy działanie; zepsuty, sort -u prawdopodobnie będzie szybszy.

Odpowiedź

Rozwiązanie awk widoczne na #bash (Freenode):

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

Komentarze

  • Właśnie wypróbowałem to na pliku 2G i zajęło to trzy minuty na moim notebooku. Nie jest zły. Próbowałem też uniq filename | awk '! widziano [$ 0] ++ ', ale nie było ' żadnego szybciej.
  • @HashWizard: to polecenie nie sortuje, ale eliminuje każde następne wystąpienie tej samej linii.
  • Zastanawiasz się, jak działa to polecenie? – Zobacz tutaj: unix.stackexchange.com/questions/159695/how-does-awk-a0-work
  • @MaxWilliams tak , działa, jeśli są losowo rozmieszczane.
  • zachowaj nowe linie lub wiersze ze spacjami awk '/^\s*?$/||!seen[$0]++'

Odpowiedź

Istnieje prosta (co nie jest oczywista) metoda wykorzystująca standardowe narzędzia, które nie wymagają dużej pamięci poza uruchomieniem sort, który w większości implementacji ma określone optymalizacje dla dużych plików (dobry algorytm sortowania zewnętrznego). Zaletą tej metody jest to, że wykonuje pętlę tylko po wszystkich wierszach wewnątrz narzędzi specjalnego przeznaczenia, nigdy w językach interpretowanych.

<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 

Jeśli wszystkie wiersze zaczynają się od znak niebędący białymi znakami, można zrezygnować z niektórych opcji:

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

W przypadku dużej liczby duplikatów metoda, która wymaga przechowywania tylko jednej kopii każda linia w pamięci będzie działać lepiej. Przy pewnym narzucie interpretacji istnieje „bardzo zwięzły skrypt awk służący do tego (już opublikowany przez enzotib ):

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

Mniej zwięźle: !seen[$0] {print} {seen[$0] += 1}, tzn. wypisz bieżący wiersz, jeśli jeszcze go nie widziano, a następnie zwiększ seen licznik dla tej linii (niezainicjowane zmienne lub elementy tablicy mają wartość liczbową 0).

W przypadku długich linii można zaoszczędzić pamięć, zachowując tylko niepodważalną sumę kontrolną (np. skrót kryptograficzny) każdego wiersza . Na przykład, używając SHA-1, potrzebujesz tylko 20 bajtów plus stały narzut na linię. Jednak przetwarzanie obliczeniowe jest raczej powolne; ta metoda odniesie sukces tylko wtedy, gdy masz szybki procesor (szczególnie taki z akceleratorem sprzętowym do obliczania skrótów) i niewiele pamięci w stosunku do rozmiaru pliku i wystarczająco długie wiersze. Żadne podstawowe narzędzie nie pozwala obliczyć sumy kontrolnej dla każdego wiersza; „musiałbyś ponieść narzut interpretacyjny Perl / Python / Ruby /… lub napisać dedykowany skompilowany program.

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

Komentarze

  • @Gilles Opierając się na twoim wyjaśnieniu awk '!seen[$0]++', czy oznacza to, że jeśli awk zobaczy 2 zduplikowane wiersze, zachowa zawsze pierwszy i zignoruje wszystkie kolejne? (Albo zachowa ostatni?)
  • @ user779159 Zachowuje pierwszy: każdy wiersz wejściowy jest albo drukowany natychmiast (pierwsze wystąpienie), albo wcale (powtórz wystąpienie).
  • Ale jak to się ma do sortowania -u …?
  • @HashWizard Zwykły sort -u zmienia kolejność.Moja odpowiedź pokazuje rozwiązania, które zachowują kolejność (a dokładniej kolejność pierwszych wystąpień).
  • @Gilles powiedziałbyś, że jest szybszy niż sort -u dla dużych plików (10G) z 50% duplikatami ?

Odpowiedź

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

Pamiętaj, że plik wyjściowy być posortowane.

Komentarze

  • Nie tak szybko, jak polecenie awk w innych odpowiedziach, ale koncepcyjnie proste!
  • @Johann Robię to dość często na plikach zawierających setki tysięcy (a nawet milionów) krótkich ciągów zakończonych znakiem nowej linii. Wyniki przeprowadzanych przeze mnie eksperymentów są bardzo szybkie. Może to być ważniejsze, jeśli zostanie użyte w skryptach, które są wielokrotnie uruchamiane, oszczędność czasu może być znaczna.
  • Użyj sort -u, aby usunąć duplikaty podczas sortowania, raczej niż później. (I oszczędza przepustowość pamięci) przesyłając go do innego programu). Jest to lepsze niż wersja awk tylko wtedy, gdy chcesz również posortować dane wyjściowe. (Operator w tym pytaniu chce, aby jego oryginalne zamówienie zostało zachowane , więc jest to dobra odpowiedź na nieco inny przypadek użycia).
  • Dla mnie zajęło to około minuty plik 5,5 miliona wierszy (łącznie 1,8 GB). Świetnie.

Odpowiedź

Zakładając, że możesz sobie pozwolić na przechowywanie w pamięci jak największej ilości zduplikowanego pliku ( jeśli dane są rzeczywiście zduplikowane 100-krotnie, co powinno stanowić około 20 MiB + narzut), możesz to bardzo łatwo zrobić za pomocą Perla.

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

To zachowuje również kolejność.

Możesz wyodrębnić liczbę wystąpień każdej linii z skrótu %dup, jeśli chcesz, jako dodatkowy darmowy bonus.

Jeśli wolisz awk, to też powinno wystarczyć (ta sama logika co w wersji Perla, ta sama kolejność, te same dane zebrane w dup zmienna):

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

Komentarze

  • To jest za dobre @Mat, I miał zamiar siorbać plik, lol ;-).
  • Teraz czekam na @ManAtWork dla jego seda i awk magic weavery 🙂
  • znowu super dla awk wskazówka: – )
  • Czy można zmienić skrypt perla na tylko remov e zduplikować sąsiednie linie?
  • @dumbledad: uniq robi to samo z siebie

Odpowiedź

Ponieważ żadna inna odpowiedź nie zapewniła wsparcia w miejscu, oto jedna:

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

Komentarze

  • Czy to zachowuje kolejność? Nawiasem mówiąc, to mi się nie udało. Moja wersja to: GNU Awk 4.0.2
  • @Leonid tak, tak. Drukuje pierwsze wystąpienie dowolnej unikalnej linii. Obsługa inplace została po raz pierwszy wprowadzona w wersji 4.1, która została wydana w 2013 roku.
  • To powinna być odpowiedź. To ' faktycznie usuwa zduplikowany ciąg w istniejącym lub bieżącym pliku, w którym górna odpowiedź i większość odpowiedzi tutaj tylko drukuje unikatowe / zduplikowane ciągi i nic nie robi, a my musimy utworzyć inne wyjście do przechowywania wyniku.

Odpowiedź

Możesz użyć uniq http://www.computerhope.com/unix/uuniq.htm

uniq zgłasza lub odfiltrowuje powtarzające się wiersze w pliku.

Komentarze

  • Podczas udzielania odpowiedzi lepiej jest podać jakieś wyjaśnienie, DLACZEGO twoja odpowiedź jest jedyną. Więc czym ta odpowiedź różni się od kilku poprzednich odpowiedzi?
  • Ze strony podręcznika uniq: Uwaga: 'uniq' does not detect repeated lines unless they are adjacent. Więc najpierw musisz ją posortować i zgubić kolejność nie zduplikowanych linii.

Odpowiedź

Python One liners:

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

Komentarze

  • powoduje to, że cały plik jest wrzucany do pamięci i może nie być dobrym rozwiązaniem dla problemu OP '. Nie ma również gwarancji zachowania porządku.
  • Dziękuję za sugestię, ' właśnie uczyłem się języka Python .. właśnie próbowałem tego w celach edukacyjnych ..:)
  • Tutaj ' s wersja Pythona 2.7, która nie jest wersją jednowierszową, ale (zwięźle) zwraca unikalne wiersze z zachowaniem kolejności bez ładowania całego pliku do pamięci lub tworzenia pojedynczego gigantycznego ciągu do podania do wydrukowania
  • Dzięki @ 1_CR Mam dziś coś do nauczenia 🙂 OrderedDict

Odpowiedź

Żadna z odpowiedzi nie działała dla mnie na moim Macu, więc napisałem prostego Pythona skrypt, który działa dla mnie. Ignoruję początkowe / końcowe spacje, a także nie obchodzi mnie zużycie pamięci.

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) 

Zapisz powyższe jako unikalne.py i uruchom w ten sposób:

python unique.py inputfile.txt outputfile.txt 

Odpowiedź

ROZWIĄZANIE BEZ UTRZYMANIA ORYGINALNEJ KOLEJNOŚCI

Zrobiłem to z następującym fragmentem kodu.

sort duplicates.txt | uniq > noDuplicates.txt 

Polecenie sort sortuje wiersze alfabetycznie, a polecenie uniq usuwa duplikaty.

UWAGA: Dlaczego najpierw posortowaliśmy wiersze, to uniq nie wykrywa zduplikowanych linii, chyba że sąsiadują ze sobą.

Komentarze

  • Pytanie dotyczy metody (najlepiej ) który utrzymuje kolejność wejściową; czy mógłbyś edytować swoją odpowiedź, aby rozwiązać ten problem? Zauważ, że istnieją odpowiedzi korzystające z sort, które zachowują kolejność wprowadzania, i jedna odpowiedź za pomocą sort bez utrzymywania kolejności wprowadzania, ale w bardziej efektywny sposób niż przesyłanie do uniq.
  • @StephenKitt Edited. Przejrzałem inne odpowiedzi, ale nie mogłem ' znaleźć niczego tylko za pomocą podstawowych poleceń. Dziękuję za opinię.
  • Dałem Ci link do odpowiedzi zawierającej tylko podstawowe polecenia, a właściwie tylko jedno polecenie, sort -u (który jest częścią POSIX ) ;-).
  • @StephenKitt Widziałem tę odpowiedź. Mój jest także sposobem na rozwiązanie problemu. Czego chcesz, żebym zrobił więcej? Czy mam usunąć odpowiedź?
  • Nie, nie usuwaj swojej odpowiedzi; Chciałem się tylko upewnić, że znasz inną odpowiedź, biorąc pod uwagę, że powiedziałeś, że „nie możesz ' znaleźć niczego tylko za pomocą podstawowych poleceń”.

Odpowiedź

Dzięki bash 4, czystemu rozwiązaniu bash, które wykorzystuje tablice asocjacyjne . Oto przykład

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 

Komentarze

  • Don ' t używaj pętli read do przetwarzania dużych plików tekstowych. bash musi czytać po jednym bajcie, aby uniknąć przekroczenia znaku nowej linii. Bash nie jest też ogólnie bardzo szybki w przetwarzaniu tekstu w porównaniu z awk. Jeśli to zrobisz, read -ra pozwoli uniknąć zjadania odwrotnych ukośników w twoich danych wejściowych. Ponadto nie ' nie zapomnij o unset llist po pętli, jeśli umieścisz to w funkcji powłoki lub używaj go interaktywnie.
  • @PeterCordes lub możesz po prostu odwołać się do tego 🙂

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *