Chcę policzyć litery A, litery C, litery G, litery „N” i „-” w pliku lub każdą literę, jeśli to konieczne, czy istnieje szybkie polecenie uniksowe, aby to zrobić?
command-line
unix
shell
characters
Kirstin
źródło
źródło
[System.IO.File]::ReadAllText("C:\yourfile.txt").ToCharArray() | Group-Object $_ | Sort Count -Descending
Get-Content "C:\eula.3082.txt" | % { $_.ToCharArray() } | Group-Object | Sort Count -Descending
Odpowiedzi:
Jeśli chcesz prawdziwej prędkości:
Jest niesamowicie szybkim pseudo-jednym-linerem.
Prosty test pokazuje, że na moim Core i7 CPU 870 @ 2.93GHz liczy on nieco ponad 600 MB / s:
W przeciwieństwie do rozwiązań obejmujących sortowanie, ten działa w stałej pamięci (4K), co jest bardzo przydatne, jeśli plik jest znacznie większy niż ram.
I oczywiście przy odrobinie smaru łokciowego możemy zgolić 0,7 sekundy:
Sieci nieco ponad 1,1 GB / s wykańczają w:
Dla porównania przetestowałem niektóre inne rozwiązania na tej stronie, które wydawały się mieć jakąś obietnicę szybkości.
Rozwiązanie
sed
/awk
wykonało dzielny wysiłek, ale zmarło po 30 sekundach. Przy tak prostym wyrażeniu regularnym spodziewam się, że będzie to błąd w sed (GNU sed wersja 4.2.1):Metoda perla również wydawała się obiecująca, ale poddałem się po uruchomieniu jej przez 7 minut
źródło
grep -o foo.text -e A -e T -e C -e G -e N -e -|sort|uniq -c
Wykona lewę jako jeden liniowiec. Potrzebne jest jednak małe wyjaśnienie.
grep -o foo.text -e A -e T -e C -e G -e N -e -
greps plik foo.text dla liter a i g oraz znak-
dla każdego znaku, który chcesz wyszukać. Drukuje również jeden znak w linii.sort
sortuje to w kolejności. To przygotowuje scenę dla następnego narzędziauniq -c
liczy zduplikowane kolejne wystąpienia dowolnej linii. W tym przypadku, ponieważ mamy posortowaną listę znaków, otrzymujemy dokładną liczbę, kiedy znaki, które wyłapaliśmy w pierwszym krokuJeśli foo.txt zawiera ciąg,
GATTACA-
to właśnie otrzymam ten zestaw poleceńźródło
-o
.Wypróbuj ten, zainspirowany odpowiedzią @ Journeyman.
Kluczem jest znajomość opcji -o dla grep . To dzieli dopasowanie, tak że każda linia wyjściowa odpowiada pojedynczej instancji wzorca, a nie całej linii dla dowolnej pasującej linii. Biorąc pod uwagę tę wiedzę, wszystko, czego potrzebujemy, to wzór do użycia i sposób na policzenie linii. Za pomocą wyrażenia regularnego możemy utworzyć rozłączny wzorzec, który będzie pasował do dowolnej z wymienionych przez Ciebie postaci:
Oznacza to „dopasuj A lub T lub C lub G lub N lub -”. Podręcznik opisuje różne składnie wyrażeń regularnych, których możesz użyć .
Teraz mamy wyjście, które wygląda mniej więcej tak:
Naszym ostatnim krokiem jest połączenie i policzenie wszystkich podobnych wierszy, które można po prostu osiągnąć za pomocą
sort | uniq -c
, jak w odpowiedzi @ Journeyman. Sortowanie daje nam następujące wyniki:Które, po przepuszczeniu
uniq -c
, w końcu przypomina to, czego chcemy:Dodatek: Jeśli chcesz zsumować liczbę znaków A, C, G, N, T i - w pliku, możesz przesłać wyjście grep
wc -l
zamiastsort | uniq -c
. Istnieje wiele różnych rzeczy, które można liczyć z niewielkimi modyfikacjami tego podejścia.źródło
Jeden linijka zliczająca wszystkie litery za pomocą Pythona:
... wytwarzając taki przyjazny wynik YAML:
Interesujące jest zobaczyć, jak w większości przypadków Python może łatwo pokonać nawet bash pod względem przejrzystości kodu.
źródło
Podobne do
awk
metody Guru :źródło
Po kilku latach korzystania z systemu UNIX bardzo dobrze biegniesz w łączeniu wielu małych operacji w celu wykonywania różnych zadań filtrowania i liczenia. Każdy ma swój styl - niektórzy lubią,
awk
ased
niektórzycut
lubtr
. Oto jak bym to zrobił:Aby przetworzyć określoną nazwę pliku:
lub jako filtr:
Działa to tak:
od -a
dzieli plik na znaki ASCII.cut -b 9-
eliminuje umieszczanie przedrostkówod
.tr " " \\n
konwertuje spacje między znakami na znaki nowej linii, aby w linii był jeden znak.egrep -v "^$"
pozbywa się wszystkich dodatkowych pustych linii, które to tworzy.sort
zbiera wystąpienia każdej postaci razem.uniq -c
zlicza liczbę powtórzeń każdej linii.Nakarmiłem to „Witaj, świecie!” następnie nowy wiersz i otrzymałem to:
źródło
sed
Część jest na podstawie odpowiedzi @ Guru , oto kolejny stosując podejścieuniq
, podobne do rozwiązania Davida Schwartza.źródło
[[:alpha:]]
zamiast.
w,sed
aby dopasować tylko znaki, a nie znaki nowej linii.[[:alpha:]]
nie powiedzie się, jeśli spróbujesz dopasować takie rzeczy, jak-
wspomniano w pytaniused -e 's/[^ATCGN-]//g' -e 's/\([ATCGN-]\)/\1\n/g' foo | sort | uniq -c
. Nie wiem jednak, jak się tam pozbyć: \Możesz połączyć
grep
iwc
zrobić to:grep
przeszukuje podany plik (-y) w celu-o
znalezienia określonego tekstu, a opcja nakazuje mu wydrukowanie tylko rzeczywistych dopasowań (tj. szukanych znaków), a nie domyślny, który polega na drukowaniu każdej linii, w której znajdował się szukany tekst znalezione na.wc
wypisuje liczbę bajtów, słów i wierszy dla każdego pliku lub w tym przypadku dane wyjściowegrep
polecenia. Ta-w
opcja nakazuje policzyć słowa, przy czym każde słowo występuje w wyszukiwanym znaku. Oczywiście-l
opcja (licząca linie) również działałaby, ponieważgrep
drukuje każde wystąpienie twojego znaku wyszukiwania w osobnej linii.Aby to zrobić dla wielu znaków jednocześnie, umieść je w tablicy i zapętl:
Przykład: w przypadku pliku zawierającego ciąg
TGC-GTCCNATGCGNNTCACANN-
wyjściowy będzie:Aby uzyskać więcej informacji, zobacz
man grep
iman wc
.Wadą tego podejścia, jak zauważa użytkownik Journeyman Geek w komentarzu, jest to, że
grep
należy uruchomić raz dla każdej postaci. W zależności od tego, jak duże są twoje pliki, może to spowodować zauważalny spadek wydajności. Z drugiej strony, gdy jest to zrobione w ten sposób, łatwiej jest szybko zobaczyć, które znaki są wyszukiwane, oraz dodawać / usuwać je, ponieważ znajdują się one w osobnej linii od reszty kodu.źródło
uniq -c
wydaje się także lepszym sposobem na uzyskanie ładnie sformatowanego wyjścia. Nie jestem guru * nix, powyższe jest właśnie tym, co udało mi się zebrać z mojej ograniczonej wiedzy i niektórych stronUżywając linii sekwencji z 22hgp10a.txt, różnica czasowa między grep i awk w moim systemie sprawia, że używanie awk jest właściwą drogą ...
[Edytuj]: Po obejrzeniu skompilowanego rozwiązania Dave'a zapomnij również o awk, ponieważ jego ukończenie w tym pliku w ~ 0,1 sekundy zapewnia pełne liczenie wielkości liter.
Wersja ghostdog bez rozróżniania wielkości liter jest ukończona w ~ 14 sekund.
Sed wyjaśniono w zaakceptowanej odpowiedzi na to pytanie .
Benchmarking jest zgodny z przyjętą odpowiedzią na to pytanie .
Przyjęta odpowiedź ghostdog74 była na to pytanie .
źródło
s/cache[letters[x]]/cache[letters[x]]+cache[toupper(letters[x])]
wydobywać, aby nie rozróżniać wielkości liter bez wpływu na jego szybkość.Myślę, że jakakolwiek przyzwoita implementacja unika sortowania. Ponieważ jednak źle jest czytać wszystko 4 razy, myślę, że można w jakiś sposób wygenerować strumień, który przechodzi przez 4 filtry, po jednym dla każdego znaku, który jest odfiltrowywany, a gdzie długości strumienia są również w jakiś sposób obliczane.
Skumulowane sumy są następnie w tmp [0-6] .txt .., więc prace są nadal w toku
W tym podejściu jest tylko 13 potoków, które konwertują do mniej niż 1 Mb pamięci.
Oczywiście moim ulubionym rozwiązaniem jest:
źródło
tr
.Nie wiedziałem
uniq
ani o tymgrep -o
, ale ponieważ moje komentarze na @JourneymanGeek i @ crazy2be miały takie wsparcie, być może powinienem przekształcić je w własną odpowiedź:Jeśli wiesz, że w pliku są tylko „dobre” znaki (te, które chcesz policzyć), możesz poszukać
Jeśli tylko niektóre znaki muszą być policzone, a inne nie (tj. Separatory)
Pierwszy używa symboli wieloznacznych wyrażeń regularnych
.
, które pasują do dowolnego pojedynczego znaku. Drugi używa „zestawu akceptowanych znaków”, bez określonej kolejności, z wyjątkiem tego, że-
musi ono nastąpić jako ostatnie (A-C
jest interpretowane jako „dowolny znak pomiędzyA
iC
). W takim przypadku wymagane są cudzysłowy, aby twoja powłoka nie próbowała rozwinąć tego, aby sprawdzić pliki jednoznakowe, jeśli takie istnieją (i wygenerować błąd „brak dopasowania”, jeśli nie istnieje).Zauważ, że „sort” ma również
-u
flagę nique, dzięki czemu zgłasza rzeczy tylko raz, ale nie ma flagi towarzyszącej do liczenia duplikatów, więcuniq
jest to rzeczywiście obowiązkowe.źródło
-
nie musi przyjść ostatni, jeśli uciekniesz z ukośnikiem:'[A\-CTGN]'
powinno działać dobrze.Głupiutki:
tr
aby usunąć (-d
) wszystkie znaki oprócz (-c
) ATCGN-iconv
przekonwertować na ucs2 (UTF16 ograniczony do 2 bajtów), aby dodać bajt 0 po każdym bajcie,tr
aby przetłumaczyć te znaki NUL na NL. Teraz każda postać ma swoją własną linięsort | uniq -c
policzyć każdą linię uniqJest to alternatywa dla niestandardowej
-o
opcji grep.źródło
Format wyjściowy nie jest najlepszy ...
Teoria operacji:
Szybkość wydaje się wynosić 60 MB / s
źródło
Przykładowy plik:
Komenda:
źródło
Łącząc kilka innych
Dodaj,
| sort -nr
aby zobaczyć wyniki w kolejności częstotliwości.źródło
Krótka odpowiedź:
Jeśli pozwalają na to okoliczności, porównaj rozmiary plików o niskim zestawie znaków z zestawem bez znaków, aby uzyskać przesunięcie i po prostu policzyć bajty.
Ach, ale splątane szczegóły:
To są wszystkie postacie ascii. Jeden bajt na. Pliki zawierają oczywiście dodatkowe metadane dla różnych rzeczy używanych przez system operacyjny i aplikację, która je utworzyła. W większości przypadków spodziewałbym się, że zajmą one tyle samo miejsca bez względu na metadane, ale starałbym się zachować identyczne okoliczności, kiedy najpierw testujesz podejście, a następnie weryfikujesz, czy masz stałe przesunięcie, zanim się o to nie martwisz. Inna gotcha polega na tym, że w łamaniu linii zwykle występują dwie ascii białe znaki, a wszelkie tabulacje lub spacje byłyby po jednym. Jeśli możesz być pewien, że będą obecne i nie ma sposobu, aby wiedzieć, ile wcześniej, przestałbym czytać teraz.
Może się to wydawać wieloma ograniczeniami, ale jeśli możesz je łatwo ustalić, wydaje mi się to najłatwiejszym / najskuteczniejszym podejściem, jeśli masz mnóstwo takich ograniczeń (co wydaje się prawdopodobne, jeśli to DNA). Sprawdzanie tony plików pod względem długości i odejmowanie stałej byłoby o wiele szybsze niż uruchamianie grep (lub podobnego) na każdym z nich.
Jeśli:
I dwie rzeczy, które mogą nie mieć znaczenia, ale najpierw sprawdziłbym
Spróbuj znaleźć przesunięcie, wykonując następujące czynności:
Porównaj pusty plik z plikiem z kilkoma łatwymi do policzenia znakami z plikiem z kilkoma innymi znakami. Jeśli odjęcie pustego pliku od obu pozostałych dwóch plików da ci liczbę bajtów pasującą do liczby znaków, to koniec. Sprawdź długości plików i odejmij tę pustą ilość. Jeśli chcesz spróbować znaleźć pliki wielowierszowe, większość redaktorów dołącza dwa specjalne znaki jednobajtowe do podziałów wierszy, ponieważ Microsoft zwykle ignoruje jeden, ale w takim przypadku musisz przynajmniej grep równie dobrze możesz to wszystko zrobić grep.
źródło
Sposób Haskell :
to działa tak:
kompilowanie i używanie:
może nie nadaje się do dużych plików.
źródło
Szybki hack Perla:
-n
: Iteruj po liniach wejściowych, ale nie drukuj dla nich niczego-l
: Automatyczne usuwanie lub dodawanie podziałów liniiwhile
: powtarzaj wszystkie wystąpienia żądanych symboli w bieżącym wierszuEND
: Na koniec wydrukuj wyniki%a
: Hash, w którym przechowywane są wartościZnaki, które w ogóle nie występują, nie zostaną uwzględnione w wyniku.
źródło