Divide el archivo de texto en líneas con un número fijo de palabras

11

Relacionado, pero sin respuestas satisfactorias: ¿Cómo puedo dividir un archivo de texto grande en partes de 500 palabras más o menos?

Estoy tratando de tomar un archivo de texto ( http://mattmahoney.net/dc/text8.zip ) con> 10 ^ 7 palabras en una sola línea, y dividirlo en líneas con N palabras cada una. Mi enfoque actual funciona, pero es bastante lento y feo (usando el script de 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

¿Algún consejo sobre cómo puedo hacer esto más rápido o más compacto?

Cory Schillaci
fuente
si lo quieres más rápido, necesitas usar algo más que bash script. Recomendaría un poco de C. Puede caber en pocas líneas.
Jakuje

Respuestas:

5

Suponiendo que su definición de palabra es una secuencia de caracteres no en blanco separados por espacios en blanco, aquí hay una awksolución para su archivo de una sola línea

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

Uso xargs(17 segundos):

xargs -n1000 <file >output

Utiliza la -nbandera de la xargscual define el número máximo de argumentos. Sólo cambia 1000a 500o lo que sea que desee limitar.

Hice un archivo de prueba con 10 ^ 7 palabras:

$ wc -w file
10000000 file

Aquí están las estadísticas de tiempo:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
caos
fuente
Esto es un poco más lento que la respuesta que acepté (21s vs 12s en mi archivo)
Cory Schillaci
1
1 excelente idea, sin embargo ten cuidado xargs's comportamiento cotización-stripping
Iruvar
Cuanto más bajo, nmás lento será, para que lo sepas. Con -n10lo cancelé después de unos 8 minutos de espera ...
don_crissti
7

Perl parece sorprendentemente bueno en esto:

Cree un archivo con 10,000,000 palabras separadas por espacios

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

Ahora, perl para agregar una nueva línea después de cada 1,000 palabras

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

Sincronización

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

La solución awk aceptada tardó poco más de 5 segundos en mi archivo de entrada.

Glenn Jackman
fuente
5

No es realmente adecuado cuando el número Nde palabras es un número grande, pero si es un número pequeño (e idealmente, no hay espacios iniciales / finales en su archivo de una línea), esto debería ser bastante rápido (por ejemplo, 5 palabras por línea):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
fuente
1
Esto está perfectamente bien con grandes números también, y cegadoramente rápido. Solo genera la pastecadena sobre la marcha. Por ejemplo:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon
@terdon: es cierto, aunque para números grandes uno tiene que construir los argumentos del comando, por ejemplo, como lo hizo a través de setetc., e incluso entonces, hay un número máximo de argumentos específicos del sistema (no estoy familiarizado con todos los sabores de pastepero Creo que con algunas implementaciones hay límites en cuanto al número de args / archivos de entrada y / o la longitud de la línea de salida ...)
don_crissti
3

El mismo comando sed puede simplificarse especificando cuántos patrones de espacio de palabras desea hacer coincidir. No tenía ningún archivo de cadena grande para probarlo, pero sin los bucles en su script original, esto debería ejecutarse tan rápido como su procesador pueda transmitir los datos. Beneficio adicional, funcionará igualmente bien en archivos de varias líneas.

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

El venerable fmt(1)comando, aunque no opera estrictamente con "un número particular de palabras", puede envolver líneas largas con bastante rapidez a un objetivo en particular (o máximo) de ancho:

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

O con perl moderno, para un número específico de palabras, digamos, 10, y asumiendo un solo espacio como límite de la palabra:

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

Los coreutils pr comando es otro candidato: la única arruga parece ser que es necesario forzar el ancho de la página para que sea lo suficientemente grande como para acomodar el ancho de salida.

Usando un archivo creado usando el generador de 10,000,000 palabras 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

donde los recuentos se confirman de la siguiente manera

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

[La solución perl de Glenn es aún un poco más rápida, ~ 1.8s en esta máquina].

conductor de acero
fuente
1

en Go lo intentaría así

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