Разбить текстовый файл на строки с фиксированным количеством слов

11

Связанные, но не удовлетворительные ответы: Как я могу разбить большой текстовый файл на куски по 500 слов или около того?

Я пытаюсь взять текстовый файл ( http://mattmahoney.net/dc/text8.zip ), содержащий> 10 ^ 7 слов, все в одной строке, и разбить его на строки по N слов в каждом. Мой текущий подход работает, но довольно медленный и уродливый (с использованием сценария оболочки):

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

Любые советы о том, как я могу сделать это быстрее или компактнее?

Кори Шиллачи
источник
если вы хотите быстрее, вам нужно использовать что-то еще, а не скрипт bash. Я бы порекомендовал немного C. Он может соответствовать нескольким строкам.
Jakuje

Ответы:

5

Предполагая, что ваше определение слова представляет собой последовательность непустых символов, разделенных пробелами, вот awkрешение для вашего однострочного файла

awk '{for (i=1; i<=NF; ++i)printf "%s%s", $i, i % 500? " ": "\n"}i % 500{print ""}' file
Iruvar
источник
11

Используйте xargs(17 секунд):

xargs -n1000 <file >output

Он использует -nфлаг, xargsкоторый определяет максимальное количество аргументов. Просто изменить , 1000чтобы 500или любой предел вы хотите.

Я сделал тестовый файл с 10 ^ 7 словами:

$ wc -w file
10000000 file

Вот статистика времени:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
хаос
источник
Это немного медленнее, чем ответ, который я принял (21 против 12 в моем файле)
Кори Шиллачи
1
Отличная идея +1, однако будьте осторожны xargsс
разоблачающим
Чем ниже, тем nмедленнее это будет, просто чтобы вы знали. С помощью -n10я отменил это после примерно 8 минут ожидания ...
don_crissti
7

Perl кажется удивительно хорош в этом:

Создайте файл с 10 000 000 пробелами

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

Теперь, Perl, чтобы добавить новую строку после каждой 1000 слов

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

тайминг

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

проверить результаты

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

Принятое решение awk заняло чуть более 5 секунд в моем входном файле.

Гленн Джекман
источник
5

Не очень подходит, когда Nчисло слов является большим числом, но если оно небольшое (и в идеале, в вашем однострочном файле нет пробелов в начале / конце), это должно быть довольно быстро (например, 5 слов в строке):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
источник
1
Это прекрасно с большими числами, и очень быстро. Просто сгенерируйте pasteстроку на лету. Например:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
Тердон
@terdon - правда, хотя для больших чисел нужно создавать аргументы команды, например, как вы это сделали или через setetc ... и даже тогда, есть максимальное количество аргументов для конкретной системы (я не знаком со всеми разновидностями, pasteно Я думаю, что в некоторых реализациях существуют ограничения в отношении количества аргументов / входных файлов и / или длины выходной строки ...)
don_crissti
3

Эту же команду sed можно упростить, указав, сколько шаблонов пространства слов вы хотите сопоставить. У меня не было больших строковых файлов для тестирования, но без циклов в исходном скрипте это должно работать так же быстро, как ваш процессор может передавать данные. Дополнительное преимущество, оно будет одинаково хорошо работать с многострочными файлами.

n=500; sed -r "s/((\w+\s){$n})/\1\n/g" <input.txt >output.txt
ciclistadan
источник
3

Достопочтенная fmt(1)команда, хотя и не строго работает с «определенным количеством слов», может довольно быстро перенести длинные строки на конкретную цель (или максимальную) ширину:

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

Или с современным perl, для определенного числа слов, скажем, 10, и принимая один пробел в качестве границы слова:

... | perl -ple 's/(.*? ){10}\K/\n/g'
thrig
источник
2

Команда coreutils pr- еще один кандидат: похоже, единственная складка заключается в том, что необходимо заставить ширину страницы быть достаточно большой, чтобы соответствовать ширине вывода.

Используя файл, созданный с помощью генератора 100000000 @ 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

где количество подтверждено следующим образом

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

[Perl-решение Гленна все еще немного быстрее, ~ 1.8с на этой машине].

steeldriver
источник
1

в го я бы попробовал вот так

//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])
}
Йелмер де Реус
источник