52 votes

Choisir au hasard des lignes d'un fichier sans le slurping avec Unix

J'ai un fichier de 10 ^ 7 lignes dans lequel je veux choisir 1/100 de lignes au hasard dans le fichier. C'est le code AWK que j'ai, mais il insuffle tout le contenu du fichier avant de commencer. La mémoire de mon PC ne peut pas gérer de tels slurps Y a-t-il une autre approche pour le faire?

 awk 'BEGIN{srand()}
!/^$/{ a[c++]=$0}
END {  
  for ( i=1;i<=c ;i++ )  { 
    num=int(rand() * c)
    if ( a[num] ) {
        print a[num]
        delete a[num]
        d++
    }
    if ( d == c/100 ) break
  }
 }' file
 

90voto

cadrian Points 4102

si vous avez autant de lignes, êtes-vous sûr de vouloir exactement 1% ou une estimation statistique serait-elle suffisante?

Dans ce deuxième cas, il suffit de randomiser à 1% à chaque ligne ...

 awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01) print $0}'
 

Si vous souhaitez la ligne d’en-tête plus un échantillon aléatoire de lignes après, utilisez:

 awk 'BEGIN {srand()} !/^$/ { if (rand() <= .01 || FNR==1) print $0}'
 

57voto

Bill Points 1408

Vous avez utilisé awk, mais je ne sais pas si c'est nécessaire. Si ce n'est pas le cas, voici une façon simple de faire w / perl (et sans charger tout le fichier en mémoire):

 cat your_file.txt | perl -n -e 'print if (rand() < .01)'
 

(forme plus simple, à partir de commentaires):

 perl -ne 'print if (rand() < .01)' your_file.txt 
 

21voto

Steven Huwig Points 8029

J'ai écrit ce code exact à en rester bouche bée, vous êtes dans la chance. C'est long partiellement, car elle préserve la saisie de la commande. Il y a probablement des améliorations de performances qui peuvent être faites.

Cet algorithme est correct sans connaître la taille de saisie à l'avance. J'ai posté une pierre de rosette ici à ce sujet. (Je n'ai pas posté cette version car il ne inutiles comparaisons.)

Thread d'origine: Soumis à votre examen -- échantillonnage aléatoire en awk.

# Waterman's Algorithm R for random sampling
# by way of Knuth's The Art of Computer Programming, volume 2

BEGIN {
    if (!n) {
        print "Usage: sample.awk -v n=[size]"
        exit
    }
    t = n
    srand()

}

NR <= n {
    pool[NR] = $0
    places[NR] = NR
    next

}

NR > n {
    t++
    M = int(rand()*t) + 1
    if (M <= n) {
        READ_NEXT_RECORD(M)
    }

}

END {
    if (NR < n) {
        print "sample.awk: Not enough records for sample" \
            > "/dev/stderr"
        exit
    }
    # gawk needs a numeric sort function
    # since it doesn't have one, zero-pad and sort alphabetically
    pad = length(NR)
    for (i in pool) {
        new_index = sprintf("%0" pad "d", i)
        newpool[new_index] = pool[i]
    }
    x = asorti(newpool, ordered)
    for (i = 1; i <= x; i++)
        print newpool[ordered[i]]

}

function READ_NEXT_RECORD(idx) {
    rec = places[idx]
    delete pool[rec]
    pool[NR] = $0
    places[idx] = NR  
}

17voto

ashawley Points 1848

Cela devrait fonctionner sur la plupart des machines GNU / Linux.

 $ shuf -n $(( $(wc -l < $file) / 100)) $file
 

Je serais surpris que la gestion de la mémoire soit faite de manière inappropriée par la commande GNU shuf.

5voto

advait Points 153

Je ne sais pas awk, mais il y a une grande technique pour la résolution d'un plus version générale du problème que vous avez décrit, et dans le cas général, il est beaucoup plus rapide que le pour une ligne dans le fichier ligne de retour si rand < 0.01 approche, de sorte qu'il pourrait être utile si vous avez l'intention d'effectuer des tâches comme ci-dessus de nombreuses (en milliers, des millions) de fois. Il est connu en tant que réservoir d'échantillonnage et cette page a une très bonne explication d'une version de celui-ci est applicable à votre situation.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X