Podziel plik tekstowy na linie ze stałą liczbą słów

11

Powiązane, ale nie zadowalające odpowiedzi: Jak mogę podzielić duży plik tekstowy na około 500 słów?

Próbuję pobrać plik tekstowy ( http://mattmahoney.net/dc/text8.zip ) zawierający> 10 ^ 7 słów w jednym wierszu i podzielić go na wiersze z N słowami w każdym. Moje obecne podejście działa, ale jest dość powolne i brzydkie (przy użyciu skryptu powłoki):

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

Wszelkie wskazówki, w jaki sposób mogę uczynić to szybszym lub bardziej kompaktowym?

Cory Schillaci
źródło
jeśli chcesz tego szybciej, musisz użyć skryptu bash. Poleciłbym trochę C. Może pasować do kilku linii.
Jakuje

Odpowiedzi:

5

Zakładając, że twoja definicja słowa jest sekwencją niepustych znaków oddzielonych spacjami, oto awkrozwiązanie dla pliku jednowierszowego

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

Użyj xargs(17 sekund):

xargs -n1000 <file >output

Wykorzystuje -nflagę, xargsktóra określa maksymalną liczbę argumentów. Wystarczy zmienić 1000się 500lub cokolwiek chcesz ograniczyć.

Zrobiłem plik testowy zawierający 10 ^ 7 słów:

$ wc -w file
10000000 file

Oto statystyki czasowe:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
chaos
źródło
To jest nieco wolniejsze niż odpowiedź, którą zaakceptowałem (21s vs 12s w moim pliku)
Cory Schillaci
1
Doskonały pomysł +1, jednak uważaj xargs„s cytat złuszczaniu zachowanie
Iruvar
Im niższa, tym nwolniej to będzie, tak więc wiesz. Z -n10anulowałem to po około 8 minutach oczekiwania ...
don_crissti
7

Perl wydaje się w tym zadziwiająco dobry:

Utwórz plik zawierający 10 000 000 słów oddzielonych spacjami

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

Teraz perl, aby dodać nowy wiersz po każdym 1000 słów

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

wyczucie czasu

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

sprawdź wyniki

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

Zaakceptowane rozwiązanie awk zajęło mi nieco ponad 5 sekund w moim pliku wejściowym.

Glenn Jackman
źródło
5

Niezbyt przydatne, gdy Nliczba słów jest dużą liczbą, ale jeśli jest to mała liczba (i idealnie, jeśli nie ma spacji początkowych / końcowych w pliku jednowierszowym), powinno to być dość szybkie (np. 5 słów w wierszu):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
źródło
1
Jest to doskonale w porządku z dużymi liczbami i oślepiająco szybko. Wystarczy wygenerować pasteciąg w locie. Na przykład:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon
@terdon - prawda, choć w przypadku dużych liczb należy budować argumenty poleceń, np. tak jak to robiłeś lub za pośrednictwem setetc ... i nawet wtedy istnieje maksymalna liczba argumentów specyficznych dla systemu (nie znam wszystkich smaków, pasteale Myślę, że z niektórych implementacjach istnieją ograniczenia co do żadnego z args / plików wejściowych i / lub wyjściowej długości linii ...).
don_crissti
3

To samo polecenie sed można uprościć, określając, ile wzorców przestrzeni słów chcesz dopasować. Nie miałem żadnych dużych plików ciągów do przetestowania, ale bez pętli w oryginalnym skrypcie powinno to działać tak szybko, jak twój procesor może przesyłać strumieniowo dane. Dodatkowa korzyść, będzie działać równie dobrze na plikach wieloliniowych.

n=500; sed -r "s/((\w+\s){$n})/\1\n/g" <input.txt >output.txt
cyklistadan
źródło
3

Czcigodne fmt(1)polecenie, choć nie działa ściśle na „określonej liczbie słów”, może dość szybko zawijać długie linie do określonej szerokości celu (lub maksymalnej):

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

Lub z nowoczesnym perlem, dla określonej liczby słów, powiedzmy 10, i przyjmując pojedynczą spację jako granicę słowa:

... | perl -ple 's/(.*? ){10}\K/\n/g'
gałązka
źródło
2

Polecenie coreutils prto kolejny kandydat: jedynym pomarszczeniem wydaje się być konieczność wymuszenia, aby szerokość strony była wystarczająco duża, aby pomieścić szerokość wyjściową.

Korzystając z pliku utworzonego za pomocą generatora 10 000 000 słów @ Glenn_Jackman,

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

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

gdzie liczby są potwierdzane w następujący sposób

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

[Rozwiązanie Perla Glenna jest wciąż trochę szybsze, ~ 1,8 s na tym komputerze].

steeldriver
źródło
1

w Go spróbowałbym tego w ten sposób

//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
źródło