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
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ż wersjaawk
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 douniq
. - @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 ounset 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 🙂
sort -u
prawdopodobnie będzie szybszy.