Teilen Sie die Textdatei in Zeilen mit einer festen Anzahl von Wörtern

11

Verwandte, aber keine zufriedenstellenden Antworten: Wie kann ich eine große Textdatei in Abschnitte mit etwa 500 Wörtern aufteilen?

Ich versuche, eine Textdatei ( http://mattmahoney.net/dc/text8.zip ) mit> 10 ^ 7 Wörtern in einer Zeile zu erstellen und in Zeilen mit jeweils N Wörtern aufzuteilen. Mein aktueller Ansatz funktioniert, ist aber ziemlich langsam und hässlich (unter Verwendung eines Shell-Skripts):

i=0
for word in $(sed -e 's/\s\+/\n/g' input.txt)
do
    echo -n "${word} " > output.txt
    let "i=i+1"

    if [ "$i" -eq "1000" ]
    then
        echo > output.txt
        let "i=0"
    fi
done

Irgendwelche Tipps, wie ich das schneller oder kompakter machen kann?

Cory Schillaci
quelle
Wenn Sie es schneller wollen, müssen Sie etwas anderes als Bash-Skript verwenden. Ich würde etwas C empfehlen. Es kann zu wenigen Zeilen passen.
Jakuje

Antworten:

5

Angenommen, Ihre Wortdefinition ist eine Folge von nicht leeren Zeichen, die durch Leerzeichen getrennt sind. Hier ist eine awkLösung für Ihre einzeilige Datei

awk '{for (i=1; i<=NF; ++i)printf "%s%s", $i, i % 500? " ": "\n"}i % 500{print ""}' file
iruvar
quelle
11

Verwendung xargs(17 Sekunden):

xargs -n1000 <file >output

Es verwendet das -nFlag xargs, dessen maximale Anzahl von Argumenten definiert wird. Wechseln Sie einfach 1000zu 500oder was auch immer Sie wollen.

Ich habe eine Testdatei mit 10 ^ 7 Wörtern erstellt:

$ wc -w file
10000000 file

Hier sind die Zeitstatistiken:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
Chaos
quelle
Dies ist etwas langsamer als die Antwort, die ich akzeptiert habe (21s vs 12s in meiner Akte)
Cory Schillaci
1
Ausgezeichnete Idee +1, aber Vorsicht vor xargsdem Zitat-Stripping-Verhalten
iruvar
Je niedriger ndesto langsamer wird es, nur damit Sie es wissen. Mit habe -n10ich es nach ca. 8 Minuten Wartezeit abgesagt ...
don_crissti
7

Perl scheint erstaunlich gut darin zu sein:

Erstellen Sie eine Datei mit 10.000.000 durch Leerzeichen getrennten Wörtern

for ((i=1; i<=10000000; i++)); do printf "%s " $RANDOM ; done > one.line

Perl fügt jetzt nach jeweils 1.000 Wörtern eine neue Zeile hinzu

time perl -pe '
    s{ 
        (?:\S+\s+){999} \S+   # 1000 words
        \K                    # then reset start of match
        \s+                   # and the next bit of whitespace
    }
    {\n}gx                    # replace whitespace with newline
' one.line > many.line

Zeitliche Koordinierung

real    0m1.074s
user    0m0.996s
sys     0m0.076s

Ergebnisse überprüfen

$ wc one.line many.line
        0  10000000  56608931 one.line
    10000  10000000  56608931 many.line
    10000  20000000 113217862 total

Die akzeptierte awk-Lösung hat in meiner Eingabedatei etwas mehr als 5 Sekunden gedauert.

Glenn Jackman
quelle
4

Nicht wirklich geeignet, wenn die NAnzahl der Wörter eine große Zahl ist, aber wenn es sich um eine kleine Zahl handelt (und im Idealfall keine führenden / nachfolgenden Leerzeichen in Ihrer einzeiligen Datei), sollte dies ziemlich schnell sein (z. B. 5 Wörter pro Zeile):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
quelle
1
Dies ist auch bei großen Zahlen vollkommen in Ordnung und unglaublich schnell. Generieren Sie einfach die pasteZeichenfolge im laufenden Betrieb. Zum Beispiel:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
Terdon
@terdon - true, obwohl man für große Zahlen die Befehlsargumente aufbauen muss, z. B. wie Sie oder über setetc ... und selbst dann gibt es eine systemspezifische maximale Anzahl von Argumenten (ich bin nicht mit allen Varianten von pasteaber vertraut Ich denke, bei einigen Implementierungen gibt es Grenzen
hinsichtlich der Anzahl der Argumente
3

Der gleiche sed-Befehl kann vereinfacht werden, indem Sie angeben, mit wie vielen Wortraummustern Sie übereinstimmen möchten. Ich hatte keine großen String-Dateien zum Testen, aber ohne die Schleifen in Ihrem ursprünglichen Skript sollte dies so schnell ausgeführt werden, wie Ihr Prozessor die Daten streamen kann. Zusätzlicher Vorteil, es funktioniert genauso gut bei mehrzeiligen Dateien.

n=500; sed -r "s/((\w+\s){$n})/\1\n/g" <input.txt >output.txt
Ciclistadan
quelle
3

Der ehrwürdige fmt(1)Befehl kann, obwohl er nicht streng mit "einer bestimmten Anzahl von Wörtern" arbeitet, ziemlich schnell lange Zeilen auf eine bestimmte Zielbreite (oder maximale Breite) umbrechen:

perl -e 'for (1..100) { print "a"x int 3+rand(7), " " }' | fmt

Oder mit modernem Perl für eine bestimmte Anzahl von Wörtern, z. B. 10, und unter der Annahme eines einzelnen Leerzeichens als Wortgrenze:

... | perl -ple 's/(.*? ){10}\K/\n/g'
Thrig
quelle
2

Der prBefehl coreutils ist ein weiterer Kandidat: Die einzige Falte scheint darin zu bestehen, dass die Seitenbreite so groß sein muss, dass sie der Ausgabebreite entspricht.

Verwenden einer Datei, die mit dem 10.000.000-Wortgenerator von @ Glenn_Jackman erstellt wurde.

$ time tr '[[:blank:]]' '\n' < one.line | pr -s' ' -W 1000000 -JaT -1000 > many.line

real    0m2.113s
user    0m2.086s
sys 0m0.411s

wobei die Zählungen wie folgt bestätigt werden

$ wc one.line multi.line 
        0  10000000  56608795 one.line
    10000  10000000  56608795 many.line
    10000  20000000 113217590 total

[Glenns Perl-Lösung ist immer noch etwas schneller, ~ 1,8 Sekunden auf diesem Computer].

Steeldriver
quelle
1

in Go würde ich es so versuchen

//wordsplit.go

//$ go run wordsplit.go bigtext.txt

package main


import (
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "strings"
)


func main() {
    myfile, err := os.Open(os.Args[0])
    if err != nil {
        log.Fatal(err)
    }
    defer myfile.Close()
    data, err := ioutil.ReadAll()
    if err != nil {
        log.Fatal(err)
    }
    words := strings.Split(data, " ")
    newfile, err := os.Create("output.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer newfile.Close()
    for i := 0; i < len(words)-10; i+10 {
        newfile.WriteString(words[i:i+10])
    }
    newfile.WriteString(words[-(len(words)%10):])
    fmt.Printf("Formatted %s into 10 word lines in output.txt", os.Args[0])
}
Jelmer de Reus
quelle