Membagi file teks menjadi beberapa baris dengan jumlah kata tetap

11

Terkait, tetapi tidak ada jawaban yang memuaskan: Bagaimana saya bisa membagi file teks besar menjadi potongan-potongan 500 kata atau lebih?

Saya mencoba untuk mengambil file teks ( http://mattmahoney.net/dc/text8.zip ) dengan> 10 ^ 7 kata semua dalam satu baris, dan membaginya menjadi baris dengan masing-masing N kata. Pendekatan saya saat ini berfungsi, tetapi cukup lambat dan jelek (menggunakan skrip shell):

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

Adakah tips tentang cara membuat ini lebih cepat atau lebih ringkas?

Cory Schillaci
sumber
jika Anda menginginkannya lebih cepat, Anda perlu menggunakan sesuatu yang lain kemudian bash script. Saya akan merekomendasikan beberapa C. Ini dapat ditampung beberapa baris.
Jakuje

Jawaban:

5

Dengan asumsi definisi kata Anda adalah urutan karakter non-kosong yang dipisahkan oleh kosong, berikut ini awksolusi untuk file baris tunggal Anda

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

Gunakan xargs(17 detik):

xargs -n1000 <file >output

Ia menggunakan -nbendera xargsyang mendefinisikan jumlah argumen maksimal. Hanya mengubah 1000ke 500atau apa pun membatasi yang Anda inginkan.

Saya membuat file uji dengan 10 ^ 7 kata:

$ wc -w file
10000000 file

Ini adalah statistik waktu:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
kekacauan
sumber
Ini sedikit lebih lambat daripada jawaban yang saya terima (21s vs 12s pada file saya)
Cory Schillaci
1
Ide bagus +1, namun berhati xargs- hatilah dengan perilaku pengupasan kutipan
iruvar
Semakin rendah semakin nlambat ini akan didapat, asal Anda tahu saja. Dengan -n10saya membatalkannya setelah sekitar 8 menit menunggu ...
don_crissti
7

Perl tampaknya cukup bagus dalam hal ini:

Buat file dengan 10.000.000 kata yang dipisahkan ruang

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

Sekarang, perl untuk menambahkan baris baru setelah setiap 1.000 kata

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

Pengaturan waktu

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

verifikasi hasil

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

Solusi awk yang diterima membutuhkan waktu lebih dari 5 detik pada file input saya.

glenn jackman
sumber
5

Tidak benar-benar cocok ketika Nbanyak kata adalah angka besar tetapi jika itu adalah angka kecil (dan idealnya, tidak ada spasi awal / akhir di file satu baris Anda) ini harus cukup cepat (mis. 5 kata per baris):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
sumber
1
Ini sangat bagus dengan jumlah besar juga, dan sangat cepat. Hanya menghasilkan pastestring dengan cepat. Misalnya:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon
@terdon - benar, meskipun untuk sejumlah besar orang harus membangun argumen perintah, misalnya, seperti yang Anda lakukan atau melalui setdll ... dan bahkan kemudian, ada sejumlah argumen maks khusus sytem (saya tidak terbiasa dengan semua rasa pastetapi Saya pikir dengan beberapa implementasi ada batasan untuk no. Args / file input dan / atau panjang jalur output ...)
don_crissti
3

Perintah sed yang sama dapat disederhanakan dengan menentukan berapa banyak pola ruang kata yang ingin Anda cocokkan. Saya tidak punya file string besar untuk mengujinya, tetapi tanpa loop di skrip asli Anda ini harus berjalan secepat prosesor Anda dapat mengalirkan data. Manfaat tambahan, itu akan berfungsi sama baiknya pada file multi-line.

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

fmt(1)Perintah yang dimuliakan , walaupun tidak sepenuhnya beroperasi pada "sejumlah kata tertentu" dapat dengan cepat membungkus garis panjang dengan tujuan (atau maksimum) lebar tertentu:

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

Atau dengan perl modern, untuk jumlah kata tertentu, ucapkan, 10, dan anggap satu spasi sebagai batas kata:

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

prPerintah coreutils adalah kandidat lain: satu-satunya kerutan tampaknya adalah bahwa perlu untuk memaksa lebar halaman menjadi cukup besar untuk mengakomodasi lebar output.

Menggunakan file yang dibuat menggunakan generator 10.000.000 kata @ 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

di mana penghitungan dikonfirmasi sebagai berikut

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

[Solusi perl Glenn masih sedikit lebih cepat, ~ 1.8s pada mesin ini].

Steeldriver
sumber
1

di Go saya akan mencobanya seperti ini

//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
sumber