Gibt es eine einfache Möglichkeit, Zeichen in Wörtern in einer Datei vom Terminal aus zu zählen?

8

Ich habe 100 Millionen Zeilen in meiner Datei.

Jede Zeile hat nur eine Spalte.

z.B

aaaaa
bb
cc
ddddddd
ee

Ich möchte die Anzahl der Zeichen auflisten

So was

2 character words - 3
5 character words - 1
7 character words - 1

usw.

Gibt es eine einfache Möglichkeit, dies im Terminal zu tun?

Giri
quelle
1
Siehe auch
Zählen von

Antworten:

20
$ awk '{ print length }' file | sort -n | uniq -c | awk '{ printf("%d character words: %d\n", $2, $1) }'
2 character words: 3
5 character words: 1
7 character words: 1

Der erste awkFilter druckt nur die Länge jeder Zeile in der aufgerufenen Datei file. Ich gehe davon aus, dass diese Datei ein Wort pro Zeile enthält.

Mit sort -n(Sortieren der Zeilen aus der Ausgabe von awknumerisch in aufsteigender Reihenfolge) und uniq -c(Zählen der Häufigkeit, mit der jede Zeile nacheinander auftritt) wird dann die folgende Ausgabe für die angegebenen Daten erstellt:

   3 2
   1 5
   1 7

Dies wird dann durch das zweite awkSkript analysiert, das jede Zeile als "X Anzahl von Zeilen mit Y Zeichen" interpretiert und die gewünschte Ausgabe erzeugt.


Die alternative Lösung besteht darin, alles awkin einem Array zu erledigen und die Anzahl der Längen beizubehalten. Es ist ein Kompromiss zwischen Effizienz, Lesbarkeit / Verständlichkeit (und damit Wartbarkeit), welche Lösung die "beste" ist.

Alternative Lösung:

$ awk '{ len[length]++ } END { for (i in len) printf("%d character words: %d\n", i, len[i]) }' file
2 character words: 3
5 character words: 1
7 character words: 1
Kusalananda
quelle
Keine Notwendigkeit, in awk zu sortieren (numerisch indizierte Arrays werden standardmäßig sortiert) (schneller).
Isaac
@ Pfeil ich weiß. Ich habe diese Lösung in meiner Antwort auskommentiert, weil Sundeep mich mit ein paar Sekunden geschlagen hat. Ich spiele auch mit meinem letzten Absatz darauf an.
Kusalananda
Ich glaube, der Kommentar sollte für die Benutzer der Lösungen nützlich sein (nicht in Ihrer Antwort (oder der von Sundeep) enthalten :-)…). Andernfalls: Fügen Sie Ihrer Antwort einen Kommentar mit dem gleichen Effekt hinzu, und ich werde meine Kommentare gerne entfernen. :-)
Isaac
10

Ein anderer Weg, alles awkalleine zu machen

$ awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' ip.txt 
2 character words - 3
5 character words - 1
7 character words - 1
  • words[length()]++ Verwenden Sie die Länge der Eingabezeile als Schlüssel, um die Anzahl zu speichern
  • END{for(k in words)print k " character words - " words[k]} Nachdem alle Zeilen verarbeitet wurden, drucken Sie den Inhalt des Arrays im gewünschten Format


Leistungsvergleich, ausgewählte Zahlen sind am besten aus zwei Läufen

$ wc words.txt
 71813  71813 655873 words.txt
$ perl -0777 -ne 'print $_ x 1000' words.txt > long_file.txt
$ du -h --apparent-size long_file.txt
626M    long_file.txt

$ time awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' long_file.txt > t1

real    0m20.632s
user    0m20.464s
sys     0m0.108s

$ time perl -lne '$h{length($_)}++ }{ for $n (sort keys %h) {print "$n character words - $h{$n}"}' long_file.txt > t2

real    0m19.749s
user    0m19.640s
sys     0m0.108s

$ time awk '{ print length }' long_file.txt | sort -n | uniq -c | awk '{ printf("%d character words - %d\n", $2, $1) }' > t3

real    1m23.294s
user    1m24.952s
sys     0m1.980s

$ diff -s <(sort t1) <(sort t2)
Files /dev/fd/63 and /dev/fd/62 are identical
$ diff -s <(sort t1) <(sort t3)
Files /dev/fd/63 and /dev/fd/62 are identical

Wenn die Datei nur ASCII-Zeichen enthält,

$ time LC_ALL=C awk '{words[length()]++} END{for(k in words)print k " character words - " words[k]}' long_file.txt > t1

real    0m15.651s
user    0m15.496s
sys     0m0.120s

Ich bin mir nicht sicher, warum sich die Zeit für perlnicht viel geändert hat. Wahrscheinlich muss die Codierung anders eingestellt werden

Sundeep
quelle
Ich habe das gerade zu meiner eigenen Lösung hinzugefügt. Löschte es, als ich deines sah. :-)
Kusalananda
Ja, ich habe darüber diskutiert, meine zu löschen, bevor ich deine Bearbeitung wieder gesehen habe :)
Sundeep
Sie müssen kein numerisch indiziertes Array sortieren . Es ist immer mit zunehmendem Index geordnet. (naja, zumindest in awk :-))
Isaac
lengthohne ()funktioniert hier einwandfrei, so dass es möglicherweise überflüssig ist, geschweifte Klammern hinzuzufügen. Ich benutze jedoch GNU awk.
Sergiy Kolodyazhnyy
2
@SergiyKolodyazhnyy yup, gnu awk Handbuch sagtIn older versions of awk, the length() function could be called without any parentheses. Doing so is considered poor practice, although the 2008 POSIX standard explicitly allows it, to support historical practice. For programs to be maximally portable, always supply the parentheses
Sundeep
5

Hier ist ein perlÄquivalent (mit - optional - sort):

$ perl -lne '
    $h{length($_)}++ }{ for $n (sort keys %h) {print "$n character words - $h{$n}"}
' file
2 character words - 3
5 character words - 1
7 character words - 1
Steeldriver
quelle
Wenn Schlüsselindizes numerisch sind: Muss das Schlüsselarray in Perl sortiert werden?
Isaac
1
@Arrow: Diese Antwort verwendet einen Hash (dh ein assoziatives Array mit Zeichenfolgenschlüsseln), und diese haben eine undefinierte Schlüsselreihenfolge, also ja. Tatsächlich ist die Antwort etwas fehlerhaft, da die Schlüssel als Zeichenfolgen und nicht als Zahlen sortiert werden. Hinzufügen {$a<=>$b}nach dem sortwürde das beheben. Alternativ könnte man ein normales Array mit numerischen Schlüsseln verwenden und einfach alle Schlüssel überspringen, bei denen der Wert Null / undefiniert ist.
Ilmari Karonen
@IlmariKaronen Danke, jetzt besser. Was für einen Unterschied machen geschweifte Zahnspangen !!
Isaac
Es wäre effizienter, ein Array anstelle eines Hashs zu verwenden. Das OP möchte Millionen von Zeilen, sodass der Aufwand für das Überprüfen und Überspringen von Nullen beim Drucken durch eine günstigere Indizierung leicht ausgeglichen werden kann.
Peter Cordes
5

Eine Alternative ein Aufruf an GNU awk, mit printf :

$ awk 'BEGIN { PROCINFO["sorted_in"] = "@ind_str_asc"}
       {c[length($0)]++}
       END{
           for(i in c){printf("%s character words - %s\n",i,c[i])}
          }' infile
2 character words - 3
5 character words - 1
7 character words - 1

Der Kernalgorithmus sammelt nur die Anzahl der Zeichen in einem Array. Der Endteil druckt die mit printf formatierten gesammelten Zählungen.

Schnell, einfach, ein einziger Anruf bei awk.

Um genau zu sein: Es wird etwas mehr Speicher verwendet, um das Array zu behalten.
Es wird jedoch keine Sortierung aufgerufen (numerische Array-Indizes werden so eingestellt, dass sie mit PROCINFO immer nach oben sortiert durchlaufen werden), und nur ein externes Programm: awkanstelle mehrerer.

Isaac
quelle
1
for inEs kann vorkommen, dass numerische Array-Indizes zumindest für einige Werte oder in einigen awk-Implementierungen in numerischer Reihenfolge angegeben werden. Dies ist jedoch nicht erforderlich, nicht traditionell und definitiv nicht universell. Es kommt oft bei winzigen Sets wie 2 oder 3 oder vielleicht 4 vor; Versuchen Sie 10 oder 20 bei jeder Woche, auf die Sie Zugriff haben (ohne PROCINFO oder WHINY_USERS in gawk), und ich wette, dass mindestens ein Fall nicht sortiert ist.
dave_thompson_085
Danke für deinen Beitrag. Verwenden Sie dies : Ich glaube, es ist jetzt sortiert. :-)
Isaac
1
@ind_str_ascsortiert als Zeichenfolgen, die für Zahlen nur dann korrekt sind, wenn sie alle einstellig sind (wie in Ihrem Beispiel); Verwenden Sie @ind_num_ascdiese Option, wenn (beliebige) Werte 10 oder mehr betragen können. Und obwohl es heute weniger ein Problem ist als früher, ist diese Funktion nur ab Version 4.0 verfügbar .
dave_thompson_085