2010-08-18 2 views
5

Ich habe ein Programm, um sehr große Dateien zu verarbeiten. Jetzt muss ich einen Fortschrittsbalken anzeigen, um den Fortschritt der Verarbeitung anzuzeigen. Das Programm arbeitet auf einer Wortebene, liest Zeile für Zeile, teilt sie in Wörter auf und verarbeitet die Wörter nacheinander. Während also das Programm läuft, kennt es die Anzahl der verarbeiteten Wörter. Wenn es die Wortzählung der Datei vorher irgendwie kennt, kann es den Fortschritt leicht berechnen.Schätzung der Wortzählung einer Datei ohne die vollständige Datei zu lesen

Das Problem ist, dass die Dateien, mit denen ich es zu tun habe, sehr groß sein können. Daher ist es keine gute Idee, die Datei zweimal zu verarbeiten, um die Gesamtzahl der Wörter zu ermitteln und den eigentlichen Verarbeitungscode auszuführen.

Also ich versuche, einen Code zu schreiben, der die Wortzählung einer Datei schätzen kann, indem Sie einen kleinen Teil davon lesen. Das ist, was ich mit (in Clojure) kommen:

(defn estimated-word-count [file] 
    (let [^java.io.File file (as-file file) 
     ^java.io.Reader rdr (reader file) 
     buffer (char-array 1000) 
     chars-read (.read rdr buffer 0 1000)] 
    (.close rdr) 
    (if (= chars-read -1) 
     0 
     (* 0.001 (.length file) 
     (-> (String. buffer 0 chars-read) tokenize-line count))))) 

Dieser Code liest die ersten 1000 Zeichen aus der Datei, erstellt einen String von ihm, tokenizes es Worte zu bekommen, die Worte zählt, und schätzt dann die Wortzahl der Datei durch Multiplikation mit der Länge der Datei und Division durch 1000.

Wenn ich diesen Code auf eine Datei mit englischem Text ausführen, bekomme ich fast korrekte Wortzahl. Aber wenn ich dies mit einer Datei mit Hindi-Text (in UTF-8 kodiert) ausführe, gibt es fast das Doppelte der tatsächlichen Wortzahl zurück.

Ich verstehe, dass dieses Problem wegen der Codierung ist. Gibt es einen Weg, es zu lösen?

SOLUTION

Als suggested by Frank ich die Byteanzahl der ersten 10.000 Zeichen bestimmen und es verwenden, um die Wortanzahl der Datei zu schätzen.

(defn chars-per-byte [^String s] 
    (/ (count s) ^Integer (count (.getBytes s "UTF-8")))) 

(defn estimate-file-word-count [file] 
    (let [file (as-file file) 
     rdr (reader file) 
     buffer (char-array 10000) 
     chars-read (.read rdr buffer 0 10000)] 
    (.close rdr) 
    (if (= chars-read -1) 
     0 
     (let [s (String. buffer 0 chars-read)] 
     (* (/ 1.0 chars-read) (.length file) (chars-per-byte s) 
      (-> s tokenize-line count)))))) 

Beachten Sie, dass dabei UTF-8-Codierung angenommen wird. Außerdem habe ich entschieden, die ersten 10000 Zeichen zu lesen, weil es eine bessere Schätzung gibt.

+0

Ich glaube, Sie sind Tokenizing mit Leerzeichen (ich bin nicht vertraut mit Glojure), das ist ein ziemlich häufiger Fehler. Nicht jede Sprache verwendet Leerräume (oder irgendetwas anderes) für Wortgrenzen. – whiskeysierra

+0

@ WilliSchönborn: Ich bin nicht Tokens mit Leerzeichen. Ich verwende die Unicode-Eigenschaft regex '[\\ p {Z} \\ p {C} \\ p {P}] +'. –

+0

Ah, ok. Seltsame Syntax. – whiskeysierra

Antwort

2

In UTF-8, Hindi-Text durchschnittlich etwa zwei Bytes pro Zeichen. Sie scheinen 1000 Zeichen zu lesen und wenden die Berechnung auf die Dateilänge in Byte an. Wenn Sie also die Sprache vorher kennen, können Sie das Verhältnis von Zeichen zu Byte kompensieren.

Andernfalls können Sie die Bytezahl der ersten 100 Zeichen bestimmen, um das Verhältnis zu schätzen. Ich kenne Clojure nicht sehr gut, aber vielleicht können Sie die aktuelle Position in der Datei als eine Bytezahl mit einer Variante einer Suchfunktion nach dem Lesen von 1000 Zeichen bestimmen?

0

Können Sie die durchschnittliche Anzahl von Bytes/Zeichen nicht mit dem Verhältnis von Zeichen lesen/Byte lesen kompensieren?

11

Warum nicht einfach den Fortschrittsbalken basierend auf den verarbeiteten Bytes anstelle einer Wortzählung erstellen. Sie kennen die Größe im Voraus, und dann besteht die Hauptschwierigkeit nur darin, die Bytes pro Wort oder Bytes pro Zeile zu erhalten, während Sie sie verarbeiten.

Der einfachste Weg, dies zu tun, ist für jede Zeile, die Sie eingelesen haben, verwenden Sie getBytes, stellen Sie die Zeichencodierung, in der die Datei geschrieben wurde, und erhalten Sie die Länge davon. Dies ist möglicherweise nicht der effizienteste Weg, um es zu tun, aber es wird sehr genau und einfach zu tun sein.

Alternativ können Sie eine feste Anzahl von Bytes gleichzeitig einlesen und dann einen Puffer verwalten, um Teilwörter und Zeilenumbrüche zu verarbeiten.

0

Wie genau muss Ihr Fortschrittsbalken sein? Ich schätze, die Antwort ist nicht "kritisch für die 0,1% genau". Überprüfen Sie in diesem Fall einfach die Größe der Datei und die Kodierung und verwenden Sie das fest codierte AVG_BYTES_PER_WORD für die Verwendung mit Ihrer Fortschrittsanzeige.