it-swarm.com.de

finde n die häufigsten Wörter in einer Datei

Ich möchte beispielsweise die 10 häufigsten Wörter in einer Textdatei finden. Erstens sollte die Lösung für Tastenanschläge optimiert werden (mit anderen Worten - meine Zeit). Zweitens für die Leistung. Folgendes habe ich bisher, um die Top 10 zu erreichen:

cat test.txt | tr -c '[:alnum:]' '[\n*]' | uniq -c | sort -nr | head  -10
  6 k
  2 g
  2 e
  2 a
  1 r
  1 k22
  1 k
  1 f
  1 eeeeeeeeeeeeeeeeeeeee
  1 d

Ich könnte ein Java, python usw.) Programm erstellen, in dem ich (Word, numberOfOccurences) in einem Wörterbuch speichere und den Wert sortiere, oder ich könnte MapReduce verwenden, aber ich optimiere für Tastenanschläge.

Gibt es irgendwelche falsch positiven Ergebnisse? Gibt es einen besseren Weg?

34
Lukasz Madon

Das ist so ziemlich die häufigste Art, "N häufigste Dinge" zu finden, außer dass Ihnen ein sort fehlt und Sie ein unentgeltliches cat haben:

tr -c '[:alnum:]' '[\n*]' < test.txt | sort | uniq -c | sort -nr | head  -10

Wenn Sie vor dem uniq -c Kein sort eingeben, erhalten Sie wahrscheinlich viele falsche Singleton-Wörter. uniq führt nur eindeutige Zeilenläufe aus, nicht die allgemeine Eindeutigkeit.

EDIT : Ich habe einen Trick vergessen, "Stoppwörter". Wenn Sie sich englischen Text ansehen (sorry, hier einsprachig nordamerikanisch), nehmen Wörter wie "von", "und", "die" fast immer die ersten zwei oder drei Plätze ein. Sie möchten sie wahrscheinlich beseitigen. Die GNU Groff-Distribution enthält eine Datei mit dem Namen eign, die eine ziemlich anständige Liste von Stoppwörtern enthält. Meine Arch-Distribution hat /usr/share/groff/current/eign, Aber ich denke ich habe auch /usr/share/dict/eign oder /usr/dict/eign in alten Unixen gesehen.

Sie können Stoppwörter wie folgt verwenden:

tr -c '[:alnum:]' '[\n*]' < test.txt |
fgrep -v -w -f /usr/share/groff/current/eign |
sort | uniq -c | sort -nr | head  -10

Ich vermute, dass die meisten menschlichen Sprachen ähnliche "Stoppwörter" benötigen, die aus aussagekräftigen Worthäufigkeitszählungen entfernt wurden, aber ich weiß nicht, wo ich vorschlagen soll, dass andere Sprachen Stoppwortlisten erstellen.

EDIT :fgrep sollte den Befehl -w Verwenden, der den Ganzwortabgleich ermöglicht. Dies vermeidet Fehlalarme bei Wörtern, die nur kurze Stopp-Werke enthalten, wie "a" oder "i".

50
Bruce Ediger

Dies funktioniert besser mit utf-8:

$ sed -e 's/\s/\n/g' < test.txt | sort | uniq -c | sort -nr | head  -10
9

Verwenden wir AWK!

Diese Funktion listet die Häufigkeit jedes Wortes in der bereitgestellten Datei in absteigender Reihenfolge auf:

function wordfrequency() {
  awk '
     BEGIN { FS="[^a-zA-Z]+" } {
         for (i=1; i<=NF; i++) {
             Word = tolower($i)
             words[Word]++
         }
     }
     END {
         for (w in words)
              printf("%3d %s\n", words[w], w)
     } ' | sort -rn
}

Sie können es in Ihrer Datei folgendermaßen aufrufen:

$ cat your_file.txt | wordfrequency

und für die Top 10 Wörter:

$ cat your_file.txt | wordfrequency | head -10

Quelle: AWK-ward Ruby

7
Sheharyar

Verwenden wir Haskell!

Das wird zu einem Sprachkrieg, nicht wahr?

import Data.List
import Data.Ord

main = interact $ (=<<) (\x -> show (length x) ++ " - " ++ head x ++ "\n")
                . sortBy (flip $ comparing length)
                . group . sort
                . words

Verwendungszweck:

cat input | wordfreq

Alternative:

cat input | wordfreq | head -10
4
BlackCap

Dies ist ein klassisches Problem, das 1986 einige Resonanz fand, als Donald Knuth implementierte eine schnelle Lösung mit Hash-Versuchen in einem 8-seitigen Programm, seine literarische Programmiertechnik zu veranschaulichen, während Doug McIlroy, der Pate von Unix-Pipes, antwortete mit einem Einzeiler, der nicht so schnell war, aber die Arbeit erledigte:

tr -cs A-Za-z '\n' | tr A-Z a-z | sort | uniq -c | sort -rn | sed 10q

Natürlich hat McIlroys Lösung die Zeitkomplexität O (N log N), wobei N eine Gesamtzahl von Wörtern ist. Es gibt viel schnellere Lösungen. Zum Beispiel:

Hier ist eine C++ - Implementierung mit der zeitlichen Obergrenze O ((N + k) log k), typischerweise - nahezu linear.

Unten finden Sie eine schnelle Python -Implementierung unter Verwendung von Hash-Wörterbüchern und Heap mit Zeitkomplexität O (N + k log Q), wobei Q eine Anzahl eindeutiger Wörter ist:

import collections, re, sys

filename = sys.argv[1]
k = int(sys.argv[2]) if len(sys.argv)>2 else 10

text = open(filename).read()
counts = collections.Counter(re.findall('[a-z]+', text.lower()))
for i, w in counts.most_common(k):
    print(i, w)

Here ist eine extrem schnelle Lösung in Rust von Anders Kaseorg.

CPU-Zeitvergleich (in Sekunden):

                                     bible32       bible256
Rust (prefix tree)                   0.632         5.284
C++ (prefix tree + heap)             4.838         38.587
Python (Counter)                     9.851         100.487
Sheharyar (AWK + sort)               30.071        251.301
McIlroy (tr + sort + uniq)           60.251        690.906

Anmerkungen:

  • bible32 ist 32-mal (135 MB), bible256 - 256-mal (1,1 GB) mit sich selbst verkettet.
  • Die nichtlineare Verlangsamung von Python-Skripten wird ausschließlich durch die Tatsache verursacht, dass Dateien vollständig im Speicher verarbeitet werden, sodass der Overhead für große Dateien immer größer wird.
  • Wenn es ein Unix-Tool gäbe, das einen Heap erstellen und n Elemente von oben auf dem Heap auswählen könnte, könnte die AWK-Lösung eine nahezu lineare Zeitkomplexität erreichen, während sie derzeit O ist (N + Q log Q).
3
Andriy Makukha

So etwas sollte funktionieren mit python, was allgemein verfügbar ist:

cat slowest-names.log | python -c 'import collections, sys; print collections.Counter(sys.stdin);'

Dies setzt Word pro Zeile voraus. Wenn es mehr gibt, sollte die Aufteilung ebenfalls einfach sein.

3
Reut Sharabani