テキストファイルを固定数の単語を含む行に分割する


11

関連していますが、満足のいく答えはありません。大きなテキストファイルを500ワード程度のチャンクに分割するにはどうすればよいですか。

私は、1行に10 ^ 7ワードを超えるテキストファイル(http://mattmahoney.net/dc/text8.zip)を取得し、それをそれぞれ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

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秒vs 12秒)
Cory Schillaci

1
優れたアイデア+1、しかし用心xargss「を引用ストリッピング行動
iruvar

値が低いnほど、速度は遅くなります。-n10私は...待っているのは約8分後にそれをキャンセル
don_crissti

7

Perlはこれで驚くほど良いようです:

スペースで区切られた10,000,000語のファイルを作成する

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

さて、perlは1,000ワードごとに改行を追加します

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単語数が多い場合はあまり適していませんが、それが少ない場合(理想的には、1行のファイルに先頭/末尾のスペースがない場合)、これは非常に高速です(たとえば、1行あたり5単語)。

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt

1
これは、数が多くても完全に問題なく、驚くほど高速です。そのpaste場で文字列を生成するだけです。例えば:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon

@terdon-true、ただし多数の場合はコマンド引数を作成する必要があります。たとえば、実行したり経由したりしてset...そしてそれでも、システム固有の最大引数数があります(私はすべてのフレーバーに精通していませんpasteが一部の実装では、args /入力ファイルおよび/または出力行の長さの数に関して制限があると思います...)
don_crissti

3

同じsedコマンドは、一致させるワードスペースパターンの数を指定することで簡略化できます。テストする大きな文字列ファイルはありませんでしたが、元のスクリプトにループがなければ、プロセッサがデータをストリーミングできるのと同じ速さで実行できます。利点が追加されました。複数行のファイルでも同様に機能します。

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

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'

2

coreutils prコマンドは別の候補です。唯一の問題は、ページ幅を出力幅に対応するのに十分な大きさにする必要があるということです。

@Glenn_Jackmanの10,000,000ワードジェネレーターを使用して作成されたファイルを使用して、

$ 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

[Glennのperlソリューションはまだ少し高速で、このマシンでは1.8秒です]。


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])
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.