Divida o arquivo de texto em linhas com número fixo de palavras

11

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

Estou tentando pegar um arquivo de texto ( http://mattmahoney.net/dc/text8.zip ) 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?

Cory Schillaci
fonte
se você quiser mais rápido, precisará usar outra coisa, em seguida, bash script. Eu recomendaria alguns C. Ele pode caber em algumas linhas.
Jakuje 4/09/15

Respostas:

5

Supondo que sua definição de palavra seja uma sequência de caracteres não em branco separados por espaços em branco, aqui está uma awksolução para seu arquivo de linha única

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

Use xargs(17 segundos):

xargs -n1000 <file >output

Ele usa a -nbandeira da xargsqual define o número máximo de argumentos. Apenas mude 1000para 500ou o limite que desejar.

Eu criei 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
caos
fonte
Isso é um pouco mais lento do que a resposta que eu aceito (21s vs 12s no meu arquivo)
Cory Schillaci
1
Excelente ideia +1, no entanto cuidado xargs's comportamento de extracção Citação
Iruvar
Quanto mais baixo, nmais lento será, só para você saber. Com -n10I cancelada após cerca de 8 minutos de espera ...
don_crissti
7

Perl parece surpreendentemente bom nisso:

Crie um arquivo com 10.000.000 de 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

Cronometragem

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.

Glenn Jackman
fonte
5

Não é realmente adequado quando Num número grande de palavras é um número grande, mas se for um número pequeno (e, idealmente, não há espaços iniciais / finais no 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
don_crissti
fonte
1
Isso é perfeitamente bom com números grandes também, e incrivelmente rápido. Apenas gere a pastestring rapidamente. Por exemplo:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon
@terdon - verdade, embora para um grande número um tem que construir os argumentos do comando, por exemplo, como você fez ou através setetc ... e mesmo assim, há um número máximo sytem específico de argumentos (eu não estou familiarizado com todos os sabores de pastemas Eu acho que com algumas implementações existem limites quanto ao não de args arquivos / de entrada e / ou comprimento da linha de saída ...).
don_crissti
3

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

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

O venerável fmt(1)comando, embora não esteja operando estritamente em "um determinado número de palavras", pode rapidamente quebrar linhas longas para uma determinada largura de objetivo (ou máxima):

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'
agitar
fonte
2

O prcomando coreutils é outro candidato: a única dobra 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 de palavras de @ 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

onde 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].

chave de aço
fonte
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])
}
Jelmer de Reus
fonte