Desenhe 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 mlinhas aleatoriamente deste arquivo de dados. Portanto, a saída deve ser dois arquivos, um é o arquivo que inclui essas mlinhas de dados e o outro inclui N-mlinhas de dados.

Existe uma maneira de fazer isso usando um comando Linux?


1
Você está preocupado com a sequência de linhas? por exemplo. Deseja manter a ordem de origem ou deseja que essa sequência seja ela mesma aleatória, bem como a escolha de linhas aleatórias?
precisa saber é o seguinte

Respostas:


18

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 $mcontendo o número de linhas.


@userunknown, sort -Rcuida da aleatoriedade. Não tenho certeza se você rebaixou a resposta para isso, mas procure na página de manual primeiro.
22412 Rob Robouou

2
Observe que sort -Rnão classifica exatamente sua entrada aleatoriamente: agrupa linhas idênticas. Portanto, se a entrada é, por exemplo foo, foo, bar, bare m = 2, em seguida, um ficheiro irá conter ambos foos e o outro irá conter ambas bars. O coreutils GNU também possui shuf, que randomiza as linhas de entrada. Além disso, você não precisa de um arquivo temporário .
Gilles 'SO- stop be evil'

por que não shuf <file> |head -n $m?
Emanuele

@emanuele: Porque precisamos da cabeça e da cauda em dois arquivos separados.
Rob Wouters

5

Esse script bash / awk escolhe linhas aleatoriamente e mantém a sequência original nos dois 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

4

Como em todas as coisas do Unix, existe um utilitário para essa TM .

Programa do dia: split
splitdividirá um arquivo de várias maneiras, -bbytes, -llinhas, -nnúmero de arquivos de saída. Nós estaremos usando a -lopção Como você deseja escolher linhas aleatórias e não apenas a primeira m, sorto arquivo será aleatoriamente primeiro. Se você quiser ler sobre sort, consulte a minha resposta aqui .

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

sort -R input_file | split -l $m output_prefix

Isso criará dois arquivos, um com mlinhas e outro com N-mlinhas, nomeado output_prefixaae output_prefixab. Certifique-se de que mé o arquivo maior que você deseja ou obterá vários arquivos de comprimento m(e um com N % m).

Se você deseja garantir o uso do 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

Edit: Chegou ao meu conhecimento que algumas sortimplementações não têm uma -Rbandeira. Se você tiver perl, você pode substituir perl -e 'use List::Util qw/shuffle/; print shuffle <>;'.


1
Infelizmente, sort -Rparece estar apenas em algumas versões do tipo (provavelmente a versão gnu). Para outras plataformas, escrevi uma ferramenta chamada 'randline' que não faz nada além de randomizar stdin. Está em beesbuzz.biz/code para quem precisa. (I tendem a embaralhar o conteúdo do arquivo bastante.)
macios

1
Observe que sort -Rnão classifica exatamente sua entrada aleatoriamente: agrupa linhas idênticas. Portanto, se a entrada é, por exemplo foo, foo, bar, bare m = 2, em seguida, um ficheiro irá conter ambos foos e o outro irá conter ambas bars. O coreutils GNU também possui shuf, que randomiza as linhas de entrada. Além disso, você pode escolher os nomes dos arquivos de saída usando heade em tailvez desplit .
Gilles 'SO- stop be evil'

4

Se você não se importa de reordenar as linhas e tiver o GNU coreutils (por exemplo, no Linux ou Cygwin não incorporado, não muito antigo desde que shufapareceu na versão 6.0), shuf("shuffle") reordena as linhas de um arquivo aleatoriamente. Assim, você pode embaralhar o arquivo e despachar as primeiras m linhas em um arquivo e o restante em outro.

Não existe uma maneira ideal de fazer esse envio. Você não pode simplesmente encadear heade tailporque headficaria mais à frente. 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, o 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 teepara 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

Portably, você pode usar o awk para despachar cada linha por vez. Observe que o awk não é muito bom em inicializar seu gerador de números aleatórios; a aleatoriedade não é apenas definitivamente não adequada para criptografia, mas nem muito boa para simulações numéricas. A semente será a mesma para todas as invocações do 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ê precisar de uma aleatoriedade melhor, poderá fazer o mesmo no Perl, que semeia seu 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

@ Gilles: Por awkexemplo: -v N=$(wc -l <file) -v m=4... e só imprime uma linha "aleatória" quando o valor aleatório é menor que $m, em vez de imprimir $mlinhas aleatórias ... Parece que perlpode estar fazendo a mesma coisa com rand , mas não conhece perlbem o suficiente para passar por um erro de compilação: erro de sintaxe na linha -e 7, próximo a ") print"
Peter.O

@ Peter.O Obrigado, é o que vem da digitação em um navegador e edição descuidada. Corrigi o código awk e perl.
Gilles 'SO- stop be evil'

Todos os três métodos funcionam bem e com rapidez .. obrigado (+1) ... Estou lentamente tentando entender o perl ... e esse é um arquivo particularmente interessante e útil dividido no shufexemplo.
Peter.O

Um problema de buffering? . Estou esquecendo de algo? O head catcombo causa perda de dados no segundo teste 3-4 a seguir ... TESTE 1-2 { for i in {00001..10000} ;do echo $i; done; } | { head -n 5000 >out1; cat >out2; } .. TESTE 3-4 { for i in {00001..10000} ;do echo $i; done; } >input; cat input | { head -n 5000 >out3; cat >out4; } ... Os wc -lresultados das saídas do TESTE 1-2 são 5000 5000 (bom), mas para TESTE 3-4 are 5000 4539 (not good) .. O differnece varia de acordo com os tamanhos de arquivo envolvidos ... Aqui está um link para o meu código de teste
Peter.O

@ Peter.O Certo novamente, obrigado. De fato, headlê adiante; o que lê adiante e não imprime é descartado. Atualizei minha resposta com soluções menos elegantes, mas (tenho certeza) de corretas.
Gilles 'SO- stop be evil'

2

Assumindo m = 7e 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 7por uma variável como $1ou $m, precisará usar seq, não a {from..to}notação -que não faz expansão de variável.

Ele funciona excluindo linha por linha do arquivo, que fica cada vez menor, de modo que o número da linha, que pode ser removido, precisa ficar cada vez menor.

Isso não deve ser usado para arquivos mais longos e para muitas linhas, pois para cada número, em média, o meio arquivo precisa ser lido para o 1º e todo o arquivo para o 2º código sed .


Ele também precisa de um arquivo com as linhas removidas.
22412 Rob

Eu pensei que "incluir essas m linhas de dados" deveria significar including themapenas as linhas originais - portanto including, não consisting ofe não usando only, mas acho que sua interpretação é o que user288609 significava. Ajustarei meu script de acordo.
usuário desconhecido

Parece bom. ``
Rob Wouters

@ usuário desconhecido: você está +1no lugar errado. Deve ser rnd=$((RANDOM%(N-i)+1))onde N = 21 no seu exemplo. Atualmente, ele causa seduma falha quando rndé avaliado 0. .. Além disso, ele não se adapta muito bem com toda a reescrita desse arquivo. por exemplo, 123 segundos para extrair 5.000 linhas aleatórias de um arquivo de 10.000 linha vs. 0,03 segundos para um método mais direto ...
Peter.O

@ Peter.O: Você está certo (corrigido) e você está certo.
usuário desconhecido
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.