Dividir arquivo de texto em linhas com número fixo de palavras

10

Relacionadas, mas sem respostas satisfatórias: Como posso dividir um arquivo de texto grande em pedaços de 500 palavras ou mais?

Estou tentando obter um arquivo de texto ( link ) com > 10 ^ 7 palavras, tudo em uma linha, e dividi-lo em linhas com N palavras cada. Minha abordagem atual funciona, mas é bastante lenta e feia (usando o 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

Alguma dica de como posso tornar isso mais rápido ou mais compacto?

    
por Cory Schillaci 04.09.2015 / 21:20

8 respostas

5

Assumindo que a sua definição de palavra é uma sequência de caracteres não-brancos separados por espaços em branco, aqui está uma solução awk para o seu arquivo de linha única

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

Use xargs (17 segundos):

xargs -n1000 <file >output

Ele usa o sinal -n de xargs , que define o número máximo de argumentos. Basta alterar 1000 para 500 ou o limite que você quiser.

Eu fiz um arquivo de teste com 10 ^ 7 palavras:

$ wc -w file
10000000 file

Aqui estão as estatísticas de tempo:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
    
por 04.09.2015 / 21:52
7

Perl parece surpreendentemente bom nisso:

Crie um arquivo com 10.000.000 palavras separadas por espaço

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

Agora, perl para adicionar uma nova linha após cada 1.000 palavras

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

Tempo

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

verificar resultados

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

A solução awk aceita levou pouco mais de 5 segundos no meu arquivo de entrada.

    
por 04.09.2015 / 22:44
4

Não é realmente adequado quando N umber de palavras é um número grande, mas se for um número pequeno (e idealmente sem espaços iniciais / finais em seu arquivo de uma linha) isso deve ser bastante rápido (por exemplo, 5 palavras por linha ):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
    
por 04.09.2015 / 23:28
3

O mesmo comando sed pode ser simplificado especificando quantos padrões de espaço de palavras você deseja corresponder. Eu não tenho nenhum arquivo de string grande para testá-lo, mas sem os loops em seu script original, ele deve rodar tão rápido quanto seu processador puder transmitir os dados. Benefício adicional, funcionará igualmente bem em arquivos com várias linhas.

n=500; sed -r "s/((\w+\s){$n})/\n/g" <input.txt >output.txt
    
por 04.09.2015 / 21:59
3

O comando venerável fmt(1) , embora não esteja operando estritamente em "um determinado número de palavras", pode rapidamente agrupar linhas longas em uma meta específica (ou máxima) de largura:

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

Ou com perl moderno, para um número específico de palavras, digamos, 10 e assumindo um único espaço como o limite da palavra:

... | perl -ple 's/(.*? ){10}\K/\n/g'
    
por 04.09.2015 / 21:42
2

O comando coreutils pr é outro candidato: a única ruga parece ser que é necessário forçar a largura da página a ser grande o suficiente para acomodar a largura da saída.

Usando um arquivo criado usando o gerador de 10.000.000 palavras do @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

em que as contagens são confirmadas da seguinte forma

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

[A solução perl de Glenn ainda é um pouco mais rápida, ~ 1.8s nesta máquina].

    
por 05.09.2015 / 00:40
1

em Go eu tentaria assim

//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])
}
    
por 04.09.2015 / 23:34