web-dev-qa-db-ja.com

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

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

私はテキストファイル( http://mattmahoney.net/dc/text8.Zip )を1行に> 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

これをより速く、またはよりコンパクトにするためのヒントはありますか?

11
Cory Schillaci

Wordの定義が空白で区切られた空白以外の文字のシーケンスであると仮定すると、単一行ファイルのawkソリューションがあります

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

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
11
chaos

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秒以上かかりました。

7
glenn jackman

単語のNumberが大きい場合はあまり適していませんが、小さい場合(理想的には、1行のファイルに先頭/末尾のスペースがない場合)、これは非常に高速です(例:1行あたり5単語) :

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

由緒あるfmt(1)コマンドは、「特定の数の単語」を厳密に操作していなくても、長い行を特定の目標(または最大)幅にかなりすばやく折り返すことができます。

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

または、最新のPerlでは、特定の単語数、たとえば10に対して、単語の境界として単一のスペースを想定します。

... | Perl -ple 's/(.*? ){10}\K/\n/g'
3
thrig

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

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

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

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

$ 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秒]。

2
steeldriver

囲碁ではこんな風にやってみます

//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])
}
1
Jelmer de Reus