grep -n | sort | sed | cut
( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F
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
- Ponieważ celem następującej operacji jest
./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.
- Z tego powodu łatwiej jest martwić się znalezieniem tych 11 znaków w zestawie 128 możliwych niż w przypadku, gdy UTF-8 jest zaangażowany w inny sposób.
grep -n ''
- To wstawia ciąg
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.
- Chociaż może to wymagać trochę przestrzeni tymczasowej (w zależności od tego, jak daleko mogą wystąpić pewne sekwencje) , nie będzie wymagało wiele w porównaniu z odpowiednim rodzajem i będzie bardzo szybkie, ponieważ wymaga zerowego śledzenia.
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
- Jeśli bieżący wiersz pasuje do
/:/
dwukropka, d
usuń go z wyjścia. W przeciwnym razie automatycznie wydrukuj bieżącą i n
zewnętrzną linię.
- I tak
sed
przycina sort
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.
- W przypadku linii, które to robią, ich pierwszy
:
rozdzielony dwukropkami -f
obszar jest cut
nieobecny - podobnie jak wszystkie grep
wstawione linie lineno.
mały test wejściowy
seq 5 | sed -ne'2,3!w /tmp/L
s/.*/a-z &\& 0-9/p' >/tmp/F
... generuje 5 wierszy próbek wejściowych. Następnie...
( export LC_ALL=C; </tmp/F \
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
)| head - /tmp[FL]
... drukuje ...
==> standard input <==
a-z 1& 0-9
a-z 4& 0-9
a-z 5& 0-9
==> /tmp/F <==
a-z 1& 0-9
a-z 2& 0-9
a-z 3& 0-9
a-z 4& 0-9
a-z 5& 0-9
==> /tmp/L <==
1
4
5
większe testy czasowe
Stworzyłem kilka całkiem dużych plików:
seq 5000000 | tee /tmp/F |
sort -R | head -n1500000 |
sort -n >/tmp/L
... które /tmp/F
wstawiają 5 mil linii i 1,5 mil linii losowo wybranych linii /tmp/L
. Potem zrobiłem:
time \
( export LC_ALL=C
grep -n '' | sort -t: -nmk1,1 ./L - |
sed /:/d\;n | cut -sd: -f2-
) <./F |wc - l
Wydrukowano:
1500000
grep -n '' \
0.82s user 0.05s system 73% cpu 1.185 total
sort -t: -nmk1,1 /tmp/L - \
0.92s user 0.11s system 86% cpu 1.185 total
sed /:/d\;n \
1.02s user 0.14s system 98% cpu 1.185 total
cut -sd: -f2- \
0.79s user 0.17s system 80% cpu 1.184 total
wc -l \
0.05s user 0.07s system 10% cpu 1.183 total
(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:
xsel -bo | cc -xc - -o cselect
Potem zrobiłem:
time \
./cselect /tmp/L /tmp/F |
wc -l
... a wyniki były ...
1500000
./cselect /tmp/L /tmp/F \
0.50s user 0.05s system 99% cpu 0.551 total
wc -l \
0.05s user 0.05s system 19% cpu 0.551 total