Chia tệp văn bản thành các dòng có số lượng từ cố định


11

Liên quan, nhưng không có câu trả lời thỏa đáng: Làm thế nào tôi có thể chia một tệp văn bản lớn thành nhiều phần 500 từ hoặc hơn?

Tôi đang cố gắng lấy một tệp văn bản ( http://mattmahoney.net/dc/text8.zip ) với> 10 ^ 7 từ tất cả trong một dòng và chia thành từng dòng với N từ. Cách tiếp cận hiện tại của tôi hoạt động, nhưng khá chậm và xấu (sử dụng shell script):

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

Bất kỳ lời khuyên về làm thế nào tôi có thể làm cho điều này nhanh hơn hoặc nhỏ gọn hơn?


Nếu bạn muốn nó nhanh hơn, bạn cần sử dụng một cái gì đó khác sau đó bash script. Tôi muốn giới thiệu một số C. Nó có thể phù hợp với vài dòng.
Jakuje

Câu trả lời:


5

Giả sử định nghĩa của bạn về từ là một chuỗi các ký tự không trống được phân tách bằng khoảng trắng, đây là một awkgiải pháp cho tệp một dòng của bạn

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

11

Sử dụng xargs(17 giây):

xargs -n1000 <file >output

Nó sử dụng -ncờ xargsxác định số lượng đối số tối đa. Chỉ cần thay đổi 1000để 500hoặc bất cứ hạn chế nào bạn muốn.

Tôi đã tạo một tệp thử nghiệm với 10 ^ 7 từ:

$ wc -w file
10000000 file

Dưới đây là số liệu thống kê thời gian:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s

Điều này chậm hơn một chút so với câu trả lời tôi chấp nhận (21 giây so với 12 giây trong hồ sơ của tôi)
Cory Schillaci

1
Ý tưởng tuyệt vời +1, tuy nhiên hãy cẩn thận xargsvới hành vi trích dẫn
iruvar

Càng thấp, nđiều này sẽ càng chậm, chỉ để bạn biết. Với -n10tôi đã hủy nó sau khoảng 8 phút chờ đợi ...
don_crissti

7

Perl có vẻ khá tốt đáng kinh ngạc về điều này:

Tạo một tệp có 10.000.000 từ được phân tách bằng dấu cách

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

Bây giờ, perl để thêm một dòng mới sau mỗi 1.000 từ

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

Thời gian

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

xác minh kết quả

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

Giải pháp awk được chấp nhận chỉ mất hơn 5 giây trên tệp đầu vào của tôi.


5

Không thực sự phù hợp khi Nsố lượng từ là một số lớn nhưng nếu đó là một số nhỏ (và lý tưởng nhất là không có khoảng trắng ở đầu / cuối trong tệp một dòng của bạn) thì điều này sẽ khá nhanh (ví dụ: 5 từ trên mỗi dòng):

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

1
Điều này là hoàn toàn tốt với số lượng lớn là tốt, và nhanh chóng nhanh chóng. Chỉ cần tạo pastechuỗi trên bay. Ví dụ:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon

@terdon - đúng, mặc dù đối với số lượng lớn, người ta phải xây dựng các đối số lệnh, ví dụ như bạn đã làm hoặc thông qua setvv ... và thậm chí sau đó, có một số lượng đối số tối đa cụ thể của sytem (Tôi không quen với tất cả các hương vị của pastenhưng Tôi nghĩ rằng với một số triển khai, có các giới hạn đối với số tệp args / đầu vào và / hoặc độ dài dòng đầu ra ...)
don_crissti

3

Lệnh sed tương tự có thể được đơn giản hóa bằng cách chỉ định có bao nhiêu mẫu không gian từ bạn muốn khớp. Tôi không có bất kỳ tệp chuỗi lớn nào để kiểm tra, nhưng không có các vòng lặp trong tập lệnh gốc của bạn, nó sẽ chạy nhanh như bộ xử lý của bạn có thể truyền dữ liệu. Đã thêm lợi ích, nó sẽ hoạt động tốt như nhau trên các tệp đa dòng.

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

3

Lệnh đáng kính fmt(1), trong khi không hoạt động nghiêm ngặt trên "một số lượng từ cụ thể" có thể nhanh chóng bao bọc các dòng dài đến một chiều rộng mục tiêu cụ thể (hoặc tối đa):

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

Hoặc với perl hiện đại, đối với một số lượng từ cụ thể, giả sử, 10 và giả sử một không gian duy nhất là ranh giới từ:

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

2

Lệnh coreutils prlà một ứng cử viên khác: nếp nhăn duy nhất dường như là bắt buộc phải có chiều rộng trang đủ lớn để phù hợp với chiều rộng đầu ra.

Sử dụng tệp được tạo bằng trình tạo 10.000.000 từ của @ 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

trong đó số lượng được xác nhận như sau

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

[Giải pháp perl của Glenn vẫn nhanh hơn một chút, ~ 1,8 giây trên máy này].


1

trong Go tôi sẽ thử nó như thế này

//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])
}
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.