Desenha aleatoriamente um certo número de linhas de um arquivo de dados

13

Eu tenho uma lista de dados, como

12345
23456
67891
-20000
200
600
20
...

Suponha que o tamanho desse conjunto de dados (ou seja, linhas de arquivo) seja N . Eu quero desenhar aleatoriamente m linhas deste arquivo de dados. Portanto, a saída deve ser dois arquivos, um é o arquivo que inclui essas m linhas de dados e o outro inclui N-m linhas de dados.

Existe uma maneira de fazer isso usando um comando do Linux?

    
por user288609 22.01.2012 / 14:44

5 respostas

18

Essa pode não ser a maneira mais eficiente, mas funciona:

shuf <file> > tmp
head -n $m tmp > out1
tail -n +$(( m + 1 )) tmp > out2

Com $m contendo o número de linhas.

    
por 22.01.2012 / 14:52
5

Este script bash / awk escolhe linhas aleatoriamente e mantém a sequência original em ambos os arquivos de saída.

awk -v m=4 -v N=$(wc -l <file) -v out1=/tmp/out1 -v out2=/tmp/out2 \
 'BEGIN{ srand()
         do{ lnb = 1 + int(rand()*N)
             if ( !(lnb in R) ) {
                 R[lnb] = 1
                 ct++ }
         } while (ct<m)
  } { if (R[NR]==1) print > out1 
      else          print > out2       
  }' file
cat /tmp/out1
echo ========
cat /tmp/out2

Saída, com base nos dados da pergunta.

12345
23456
200
600
========
67891
-20000
20
    
por 22.01.2012 / 16:15
4

Assim como todas as coisas do Unix, há um utilitário para essa TM .

Programa do dia: split e split dividirá um arquivo de muitas maneiras diferentes, -b bytes, -l linhas, -n número de arquivos de saída. Nós estaremos usando a opção -l . Como você deseja escolher linhas aleatórias e não apenas o primeiro m ,% ar_de% o arquivo será aleatoriamente primeiro. Se você quiser ler sobre sort , consulte minha resposta aqui .

Agora, o código atual. É bem simples, na verdade:

sort -R input_file | split -l $m output_prefix

Isso criará dois arquivos, um com sort linhas e um com m linhas, denominado N-m e output_prefixaa . Certifique-se de que output_prefixab seja o arquivo maior que você deseja ou você obterá vários arquivos de comprimento m (e um com m ).

Se você quiser garantir o tamanho correto, aqui está um pequeno código para fazer isso:

m=10 # size you want one file to be
N=$(wc -l input_file)
m=$(( m > N/2 ? m : N - m ))
sort -R input_file | split -l $m output_prefix

Editar: chegou ao meu conhecimento que algumas implementações de N % m não têm um sinalizador sort . Se você tiver -R , poderá substituir perl .

    
por 22.01.2012 / 17:37
4

Se você não se importa em reordenar as linhas e você tem o GNU coreutils (ou seja, no Linux não-embarcado ou no Cygwin, não muito antigo, pois shuf apareceu na versão 6.0), shuf (" shuffle ") reordena as linhas de um arquivo aleatoriamente. Assim, você pode embaralhar o arquivo e distribuir as primeiras m linhas em um arquivo e o restante em outro.

Não há uma maneira ideal de fazer isso. Você não pode apenas encadear head e tail porque head seria armazenado em buffer. Você pode usar split , mas não tem flexibilidade com relação aos nomes dos arquivos de saída. Você pode usar awk , claro:

<input shuf | awk -v m=$m '{ if (NR <= m) {print >"output1"} else {print} }'

Você pode usar sed , que é obscuro, mas possivelmente mais rápido para arquivos grandes.

<input shuf | sed -e "1,${m} w output1" -e "1,${m} d" >output2

Ou você pode usar tee para duplicar os dados, se sua plataforma tiver /dev/fd ; tudo bem se m for pequeno:

<input shuf | { tee /dev/fd/3 | head -n $m >output1; } 3>&1 | tail -n +$(($m+1)) >output2

Portável, você pode usar o awk para enviar cada linha por vez. Note que o awk não é muito bom em inicializar seu gerador de números aleatórios; a aleatoriedade não é definitivamente não adequada para criptografia, mas nem mesmo é muito boa para simulações numéricas. A semente será a mesma para todas as invocações de awk em qualquer sistema dentro de um período de um segundo.

<input awk -v N=$(wc -l <input) -v m=3 '
    BEGIN {srand()}
    {
        if (rand() * N < m) {--m; print >"output1"} else {print >"output2"}
        --N;
    }'

Se você precisa de melhor aleatoriedade, você pode fazer a mesma coisa em Perl, que semeia o RNG decentemente.

<input perl -e '
    open OUT1, ">", "output1" or die $!;
    open OUT2, ">", "output2" or die $!;
    my $N = 'wc -l <input';
    my $m = $ARGV[0];
    while (<STDIN>) {
        if (rand($N) < $m) { --$m; print OUT1 $_; } else { print OUT2 $_; }
        --$N;
    }
    close OUT1 or die $!;
    close OUT2 or die $!;
' 42
    
por 23.01.2012 / 01:43
2

Presumindo m = 7 e N = 21 :

cp ints ints.bak
for i in {1..7}
do
    rnd=$((RANDOM%(21-i)+1))
    # echo $rnd;  
    sed -n "${rnd}{p,q}" 10k.dat >> mlines 
    sed -i "${rnd}d" ints 
done

Nota: Se você substituir 7 por uma variável como $1 ou $m , será necessário usar seq , não a {from..to} -notation, que não faz expansão variável.

Funciona excluindo linha por linha do arquivo, que fica cada vez mais curto, de modo que o número da linha, que pode ser removido, tenha que ficar menor e menor.

Isso não deve ser usado para arquivos mais longos e muitas linhas, já que para cada número, em média, o meio arquivo precisa ser lido para o primeiro, e o arquivo inteiro para o segundo arquivo sed código.

    
por 22.01.2012 / 15:19