Diviser le fichier texte en lignes avec un nombre fixe de mots

11

Connexes, mais pas de réponses satisfaisantes: Comment puis-je diviser un gros fichier texte en morceaux de 500 mots environ?

J'essaie de prendre un fichier texte ( http://mattmahoney.net/dc/text8.zip ) avec> 10 ^ 7 mots tous sur une seule ligne, et de le diviser en lignes de N mots chacune. Mon approche actuelle fonctionne, mais est assez lente et laide (en utilisant un script 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

Des conseils sur la façon de rendre cela plus rapide ou plus compact?

Cory Schillaci
la source
si vous le voulez plus vite, vous devez utiliser autre chose que le script bash. Je recommanderais quelques C. Il peut s'adapter à quelques lignes.
Jakuje

Réponses:

5

En supposant que votre définition de mot est une séquence de caractères non vides séparés par des blancs, voici une awksolution pour votre fichier sur une seule ligne

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

Utilisation xargs(17 secondes):

xargs -n1000 <file >output

Il utilise le -ndrapeau xargsdont définit le nombre maximum d'arguments. Il suffit de changer 1000de 500ou quoi que vous voulez limiter.

J'ai fait un fichier de test avec 10 ^ 7 mots:

$ wc -w file
10000000 file

Voici les statistiques de temps:

$ time xargs -n1000 <file >output
real    0m16.677s
user    0m1.084s
sys     0m0.744s
le chaos
la source
C'est un peu plus lent que la réponse que j'ai acceptée (21s contre 12s sur mon dossier)
Cory Schillaci
1
Excellente idée +1, méfiez - vous cependant xargs« de comportement de devis de décapage
Iruvar
Le plus bas nsera le plus lent, juste pour que vous le sachiez. Avec -n10je l'ai annulé après environ 8 minutes d'attente ...
don_crissti
7

Perl semble étonnamment bon dans ce domaine:

Créer un fichier avec 10 000 000 de mots séparés par des espaces

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

Maintenant, perl pour ajouter une nouvelle ligne après chaque 1000 mots

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

Horaire

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

vérifier les résultats

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

La solution awk acceptée a pris un peu plus de 5 secondes sur mon fichier d'entrée.

glenn jackman
la source
5

Ne convient pas vraiment lorsque le nombre Nde mots est un grand nombre, mais si c'est un petit nombre (et idéalement, aucun espace de début / de fin dans votre fichier d'une ligne), cela devrait être assez rapide (par exemple 5 mots par ligne):

tr -s '[[:blank:]]' '\n' <input.txt | paste -d' ' - - - - - >output.txt
don_crissti
la source
1
C'est parfaitement bien avec de grands nombres aussi, et d'une rapidité aveuglante. Générez simplement la pastechaîne à la volée. Par exemple:tr -s '[[:blank:]]' '\n' < text8 | paste -d' ' $(perl -le 'print "- " x 1000')
terdon
@terdon - vrai, bien que pour les grands nombres, il faut construire les arguments de commande par exemple comme vous l'avez fait ou via setetc ... et même dans ce cas, il y a un nombre maximum d'arguments spécifique au système (je ne connais pas toutes les saveurs de pastemais Je pense qu'avec certaines implémentations, il y a des limites quant au nombre d'arguments / fichiers d'entrée et / ou la longueur de la ligne de sortie ...)
don_crissti
3

La même commande sed peut être simplifiée en spécifiant le nombre de modèles d'espace de mots que vous souhaitez faire correspondre. Je n'avais pas de gros fichiers de chaîne pour le tester, mais sans les boucles de votre script d'origine, cela devrait fonctionner aussi vite que votre processeur peut diffuser les données. Avantage supplémentaire, cela fonctionnera aussi bien sur les fichiers multi-lignes.

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

La vénérable fmt(1)commande, bien qu'elle ne fonctionne pas strictement sur "un nombre particulier de mots", peut assez rapidement enrouler de longues lignes à un objectif particulier (ou maximum) de largeur:

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

Ou avec perl moderne, pour un nombre spécifique de mots, disons 10, et en supposant un seul espace comme limite de mot:

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

La prcommande coreutils est un autre candidat: la seule ride semble être qu'il faut forcer la largeur de page pour qu'elle soit suffisamment grande pour accueillir la largeur de sortie.

En utilisant un fichier créé à l'aide du générateur de 10 000 000 de mots 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

où les dénombrements sont confirmés comme suit

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

[La solution Perl de Glenn est encore un peu plus rapide, ~ 1,8 s sur cette machine].

tournevis
la source
1

dans Go je l'essayerais comme ça

//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
la source