将文本文件拆分为固定单词数的行


11

相关但没有令人满意的答案:如何将大文本文件拆分为500个单词左右的块?

我试图将一个文本文件(http://mattmahoney.net/dc/text8.zip)一行都包含> 10 ^ 7个单词,然后将其拆分为N个单词的行。我当前的方法可行,但相当缓慢且难看(使用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

关于如何使它更快或更紧凑的任何提示?


如果您想要更快,则需要使用其他东西,然后使用bash脚本。我会推荐一些C。它可以容纳几行。
Jakuje 2015年

Answers:


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定义最大参数数。只需更改1000500或任意限制即可。

我用10 ^ 7个单词制作了一个测试文件:

$ wc -w file
10000000 file

以下是时间统计信息:

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

这比我接受的答案要慢一些(我的档案是21秒对12秒)
Cory Schillaci

1
优秀的想法+1,谨防不过xargs报价剥离行为
iruvar

越低,n速度越慢,这就是您所知道的。与-n10我取消约8分钟的等待后取消
don_crissti 2015年

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单词的数量很大时并不合适,但是如果单词的数量很小(理想情况下,单行文件中没有前导/后缀空格),则应该很快(例如每行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 -真实的,但对于大量的人有建立命令参数,例如像你一样,或通过set等......即使如此,还有的参数的系统正特定的最大数量(我不熟悉的所有口味paste,但我认为在某些实现中,args /输入文件和/或输出
行长的数量是有限的

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.8s]。


1

在Go中,我会这样尝试

//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])
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.