Biorąc pod uwagę plik L z jedną nieujemną liczbą całkowitą na wiersz i plik tekstowy F, jaki byłby szybki sposób na zachowanie tylko tych wierszy w F, których numer pojawia się w pliku L?
Przykład:
$ cat L.txt
1
3
$ cat F.txt
Hello World
Hallo Welt
Hola mundo
$ command-in-question -x L.txt F.txt
Hello World
Hola mundo
Szukam polecenia, które może obsłużyć plik L zawierający 500 milionów lub więcej wpisów; plik L jest sortowany numerycznie.
Uwaga: Jestem w połowie wdrażania, command-in-question
ale zastanawiałem się, czy można tu również użyć niektórych narzędzi uniksowych.
Aktualizacja: Dziękuję za wszystkie odpowiedzi, dzisiaj wiele się nauczyłem! Chciałbym zaakceptować więcej niż jedną odpowiedź, ale nie jest to możliwe.
Odpowiedzi:
Z
C
pominięciem istotnych komunikatów o błędach:źródło
xsel -bo | cc -xc - -o cselect
. I to po prostu działało - potrzebuje tylko dwóch bibliotek.LINE_MAX
swoją wersję, więc prawdopodobnie pracujesz z bardzo dużymi liniami w swoich plikach. Zaktualizowałem A z wersją używanągetline()
do usunięcia limitu rozmiaru linii.LINE_MAX
, więcgetline
wydaje się być w porządku.Używałbym
awk
, ale nie zapisywałbym całej zawartościL.txt
w pamięci i robiłbym niepotrzebne wyszukiwanie hash ;-).źródło
n
, inaczej (jak jest) nie trafi1
wL.txt
command-in-question
skrypcie, to nie możesz mieć wbudowanej nazwy pliku w kodzie.-v list="$opt_x"
nie działa również z powodu wykonywania odwrotnego ukośnika przez awk na nim. Dlatego właśnie tutaj używam ENVIRON.grep -n | sort | sed | cut
To powinno działać dość szybko (niektóre testy czasowe są zawarte poniżej) z wejściem dowolnego rozmiaru. Kilka uwag na temat:
export LC_ALL=C
./F
ułożenie całego pliku w stos z./L
plikiem lineno, jedynymi znakami, o które naprawdę będziemy musieli się martwić, są[0-9]
cyfry ASCII i:
dwukropek.grep -n ''
LINENO:
do nagłówka każdej linii w stdin - lub<./F
.sort -t: -nmk1,1 ./L -
sort
w ogóle zaniedbuje sortować swoje pliki wejściowe, a zamiast tego (poprawnie) zakłada, że są one wstępnie sortowane i-m
ustawia je w-numerically
porządku posortowanym, ignorując w zasadzie wszystko poza jakimkolwiek potencjalnie-k1,1
występującym-t:
znakiem dwukropka.sort
wyświetli pojedynczy strumień, w którym dowolne wejście lineno./L
będzie bezpośrednio poprzedzać odpowiednie wiersze w./F
../L
Linie zawsze są na pierwszym miejscu, ponieważ są krótsze.sed /:/d\;n
/:/
dwukropka,d
usuń go z wyjścia. W przeciwnym razie automatycznie wydrukuj bieżącą in
zewnętrzną linię.sed
przycinasort
wyniki tylko do sekwencyjnych par linii, które nie pasują do dwukropka i następnej linii - lub, tylko do linii od./L
i do następnej.cut -sd: -f2-
cut
-s
wypisuje z wyjścia te z linii wejściowych, które nie zawierają co najmniej jednego z-d:
łańcuchów eliminatora - a zatem./L
linie są całkowicie przycinane.:
rozdzielony dwukropkami-f
obszar jestcut
nieobecny - podobnie jak wszystkiegrep
wstawione linie lineno.mały test wejściowy
... generuje 5 wierszy próbek wejściowych. Następnie...
... drukuje ...
większe testy czasowe
Stworzyłem kilka całkiem dużych plików:
... które
/tmp/F
wstawiają 5 mil linii i 1,5 mil linii losowo wybranych linii/tmp/L
. Potem zrobiłem:Wydrukowano:
(Dodałem tam odwrotne ukośniki)
Spośród obecnie oferowanych rozwiązań jest to najszybsze ze wszystkich, ale jedno w porównaniu z zestawem danych wygenerowanym powyżej na mojej maszynie. Z pozostałych tylko jeden był bliski rywalizacji o drugie miejsce, a to właśnie
perl
tutaj .Nie jest to bynajmniej oryginalne oferowane rozwiązanie - skróciło 1/3 czasu realizacji dzięki poradom / inspiracjom oferowanym przez innych. Zobacz historię postów dla wolniejszych rozwiązań (ale dlaczego?) .
Warto również zauważyć, że niektóre inne odpowiedzi mogłyby równie dobrze konkurować, gdyby nie architektura wielu procesorów mojego systemu i jednoczesne wykonanie każdego z procesów w tym potoku. Wszystkie działają w tym samym czasie - każdy na własnym rdzeniu procesora - przekazując dane i wykonując niewielką część całości. To całkiem fajne.
ale najszybszym rozwiązaniem jest ...
Ale to nie jest najszybsze rozwiązanie. Najszybszym rozwiązaniem oferowanym tutaj, ręce w dół, jest program C . Nazwałem to
cselect
. Po skopiowaniu go do mojego schowka X skompilowałem go w następujący sposób:Potem zrobiłem:
... a wyniki były ...
źródło
sed -ne'/:/!{n;p;}' | cut -d: -f2-
zamiastsed -ne'/:/!N;/\n/s/[^:]*://p'
sed
s -sed
używam to pamiątkased
- możesz zobaczyćalias
wartość wtime
wynikach. Nawiasem mówiąc, mój pakiet rodowy jest kompilowany statycznie z musl libc - implementacją wyrażeń regularnych opartą na TRE . Kiedy przestawię go na GNUsed
- i uruchomię go bezcut
- dodaje on pełną sekundę do czasu zakończenia (2,8 s) - sumuje go o ponad jedną trzecią. A to tylko 0,3 sekundy szybciej niż twoje w moim systemie.sort -mn
w przeciwieństwie dosort -nmk1,1
może być lepiej, ponieważ nie trzeba tutaj rozdzielać (nie testowano)-n
jest wyspecyfikowany, aby zrobić pierwszy ciąg liczbowy w wierszu, więc pomyślałem, ok-mn
lub-nm
, z jakiegokolwiek powodu, jedyny raz, kiedy spadł poniżej 2 sekund w czasie ukończenia, kiedy dodałem wszystkie opcje bez zmian. To dziwne - i to jest powód, dla którego wczoraj nie zajmowałem się tym-m
- wiedziałem, o co mi chodzi, ale wydawało się, że po prostu działa jako coś w rodzaju automatycznej optymalizacji. Co ciekawe, pamiątkasort
ma-z
opcję długości sznurka, która dotyczy tylko-[cm]
...-n
nie jest pierwszym ciągiem liczbowym w linii . Po prostu uważa, że linia jest liczbą, więcabc 123
będzie wynosić 0. Więc nie może być mniej wydajna niż z-t: -k1,1
Użyłbym
awk
:Aktualizacja: wykonałem pomiary wydajności; wydaje się, że ta wersja skaluje się jeszcze lepiej przy bardzo dużych zestawach danych (jak w przypadku podanych wymagań), ponieważ porównanie jest bardzo szybkie i nadmiernie rekompensuje wysiłek konieczny do zbudowania tabeli skrótów.
źródło
awk
mogą być w stanie obsłużyć tak ogromne zbiory danych. - Używam GNUawk
i nie ma problemów; test z 500 milionami linii danych wymagał 7 minut.real 16m3.468s
-user 15m48.447s
-sys 0m10.725s
. Zużyło 3,3 GB pamięci RAM, testując rozmiar 1/10L
z 50 000 000 linii; orazF
z 500 000 000 linii - w porównaniu do czasu dla awk anser Stéphane'a Chazelasa:real 2m11.637s
-user 2m2.748s
-sys 0m6.424s
- Nie używam szybkiego pudełka, ale porównanie jest interesujące.seq
wyjście, a potem mniejsze, losowo wybrany podzbiór samo w L .Dla kompletności: możemy połączyć doskonały skrypt awk w odpowiedzi Stéphane'a Chazelasa i skrypt perla w odpowiedzi kos, ale bez zachowania całej listy w pamięci, w nadziei, że perl może być szybszy niż awk. (Zmieniłem kolejność argumentów, aby pasowała do pierwotnego pytania).
źródło
awk
. Jest prawie tak szybki jak mój - testowałem oba teraz trzy razy i za każdym razem mój mój zestaw testowy linii o długości 5 mil w 1,8 ... sekundach i twój 1,9 ... sekund za każdym razem. Kod genowy zestawu testowego jest w mojej odpowiedzi, jeśli cię to obchodzi, ale chodzi o to, że jest bardzo dobry. Co więcej, wynik jest prawidłowy - nadal nie mogę wykonaćawk
pracy ... Mimo to obie nasze odpowiedzi są zawstydzone przez FloHimself .awk
s. Na twojej próbie otrzymuję 1,4s z gawk (4s dla Janis), 0,9s z mawk, 1,7s z tym rozwiązaniem perla, 2,3s z kos ', 4,5s z twoim (GNU sed) i 1,4s z twoim ( GNU sed) i moja sugerowana poprawa (i 0,5 s dla rozwiązania C).Napisałem prosty skrypt Perla, aby to zrobić:
Usage: script.pl inputfile_f inputfile_f
F.txt
L.txt
L.txt
w tablicyF.txt
wiersz po wierszu, śledząc jego bieżący numer wiersza i bieżący indeks tablicy; zwiększaF.txt
bieżący numer linii; jeśliF.txt
bieżący numer linii pasuje do zawartości tablicy przy bieżącym indeksie tablicy, drukuje bieżącą linię i zwiększa indeksUwagi dotyczące kosztów i złożoności :
Biorąc pod uwagę koszt wykonania przypisań, koszt dokonania porównań i koszt wydrukowania linii, biorąc pod uwagę N 1 jako liczbę linii
F.txt
i N 2 jako liczbę liniiL.txt
,while
pętla działa najwyżej N 1 razy, prowadząc do przypisań 2N 1 + N 2 (oczywiście przy założeniu N 1 > N 2 ), porównań 2N 1 i wydruków N 2 ; podany jako równy koszt każdej operacji, całkowity koszt uruchomieniawhile
pętli wynosi 4N 1 + 2N 2 , co prowadzi do złożoności skryptu O (N).Przetestuj plik wejściowy o wielkości 10 milionów wierszy :
Używając pliku 10 milionów linii
F.txt
zawierającego losowe linie o długości 50 znaków oraz pliku 10 milionów liniiL.txt
zawierającego liczby od 1 do 10000000 (najgorszy scenariusz):źródło
Ten roztwór perlowy jest szybszy niż inne roztwory awk lub perl o około 20%, ale owalnie nie tak szybki jak roztwór w C.
źródło
Ponieważ L.txt jest posortowane, możesz użyć join. Po prostu numeruj każdą linię w F.txt, połącz dwa pliki, a następnie usuń numer linii. Nie są potrzebne duże pliki pośrednie.
W rzeczywistości powyższe spowoduje zmieszanie linii danych, zastępując całą białą spację jedną spacją. Aby linia pozostała nienaruszona, musisz wybrać jako separator znak, który nie pojawia się w twoich danych, np. „|”. Cmd jest wtedy
Pierwszy sed usuwa początkowe spacje z wyjścia „cat -n” i zastępuje tabulator. Drugi sed usuwa numer linii i „|”.
źródło
join L.txt <(nl F.txt )
ale nie działa na dużych plikach. Nawiasem mówiąc, witamy na stronie, często nie otrzymujemy tak jasnych i dobrze sformatowanych odpowiedzi od nowych użytkowników!join
/comm
nie może działać z sortowaniem numerycznym.join -t' ' <(<L.txt awk '{printf("%010s\n",$0)}') <(<F.txt awk '{printf("%010s %s\n",NR,$0)}') | cut -d' ' -f2-
- To było powolne! - i nawet kiedy wprowadziłem przygotowane pliki z odpowiednimi klawiszami 0-paddedjoin -t' ' L.txt F.txt | cut -d' ' -f2-
, to wciąż było wolne (nie uwzględniając czasu przygotowania) - wolniej niżawk
odpowiedź @Janis (gdzie zamieściłem komentarz na temat faktycznych czasów obu odpowiedź jego i @ StéphaneChazelasjoin
+ był w porównaniu do Stéphane'a Chazelasa wykorzystującego 50 milionów linii, 500 milionów linii.awk printf
real 20m11.663s user 19m35.093s sys 0m10.513s
real 2m11.637s user 2m2.748s sys 0m6.424s
L
F
Dla kompletności kolejna próba
join
rozwiązania:Działa to poprzez sformatowanie kolumny z numerem wiersza, która się łączy, jako stałej długości z wiodącymi zerami, dzięki czemu liczby mają zawsze 15 cyfr. To omija problem łączenia, który nie lubi normalnej numerycznej kolejności sortowania, ponieważ kolumna została skutecznie zmuszona do sortowania słownikowego.
nl
służy do dodawania numerów wierszy w tym formacie do F.txt. Niestetysed
należy go użyć do sformatowania numeracji w języku L.txt.To podejście wydaje się działać poprawnie na danych testowych wygenerowanych przy użyciu metody @ mikeserv. Ale wciąż jest bardzo wolny - rozwiązanie c jest 60 razy szybsze na moim komputerze. około 2/3 czasu spędza w,
sed
a 1/3 wjoin
. Być może istnieje lepszy wyraz sed ...źródło
nl
super fajny, ale nie można solidnie użyć go na niesprawdzonych wejściach. Jedną z rzeczy, które sprawiają, że jest tak fajny, jest jego logiczna-d
eliminacja stron . Domyślnie, jeśli na wejściu jest jakikolwiek wiersz składający się tylko z ciągów:\`
(ale bez / z końcowego grobu) 1, 2, 3 lub 3 razy z rzędu, twoje liczby będą trochę szalone. Eksperymentuj w / it - jest całkiem fajnie. W szczególności spójrz na to, co się dzieje, gdy nl` czyta linię z 1 ciągiem separatora, a następnie kolejny w / 3 lub 2Ponieważ zaakceptowana odpowiedź jest w języku C, uznałem, że można wrzucić tutaj rozwiązanie python:
W przypadku korzystania z zewnętrznej biblioteki, takiej jak numpy, rozwiązanie wyglądałoby jeszcze bardziej elegancko:
źródło