AWK: obter linhas aleatórias de arquivo satisfazendo uma condição?

6

Estou tentando obter um número definido de linhas aleatórias que satisfazem uma condição.

por exemplo. se meu arquivo fosse:

a    1    5
b    4    12
c    2    3
e    6    14
f    7    52
g    1    8

então eu gostaria exatamente de duas linhas aleatórias onde a diferença entre a coluna 3 e a coluna 2 é maior que 3 mas menor que 10 (por exemplo, linhas começando com a, b, e, eg qualificariam)

Como eu abordaria isso?

awk (if something and random) '{print $1,$2,$3}'

    
por SumNeuron 22.06.2017 / 22:22

4 respostas

11

Você pode fazer isso em awk , mas obter a seleção aleatória de linhas será complexo e exigirá a gravação de um pouco de código. Em vez disso, usaria awk para obter as linhas que correspondem aos seus critérios e, em seguida, use a ferramenta padrão shuf para escolher uma seleção aleatória:

$ awk '$3-$2>3 && $3-$2 < 10' file | shuf -n2
g    1    8
a    1    5

Se você executar isso algumas vezes, verá uma seleção aleatória de linhas:

$ for i in {1..5}; do awk '$3-$2>3 && $3-$2 < 10' file | shuf -n2; echo "--";  done
g    1    8
e    6    14
--
g    1    8
e    6    14
--
b    4    12
g    1    8
--
b    4    12
e    6    14
--
e    6    14
b    4    12
--

A ferramenta shuf faz parte dos utilitários do GNU, portanto deve ser instalada por padrão na maioria dos sistemas Linux e facilmente disponível para a maioria dos * nix.

    
por 22.06.2017 / 22:38
4

Se você quiser uma resposta awk pura que apenas repita a lista uma vez:

awk -v count=2 'BEGIN { srand() } $3 - $2 > 3 && $3 - $2 < 10 && rand() < count / ++n { if (n <= count) { s[n] = $0 } else { s[1+int(rand()*count)] = $0 } } END { for (i in s) print s[i] }' input.txt

Armazenado em um arquivo para facilitar a leitura:

BEGIN { srand() }
$3 - $2 > 3 &&
$3 - $2 < 10 &&
rand() < count / ++n {
    if (n <= count) {
        s[n] = $0 
    } else { 
        s[1+int(rand()*count)] = $0 
    } 
} 
END { 
    for (i in s) print s[i] 
}

O algoritmo é uma pequena variação no algoritmo de Knuth R ; Tenho certeza que a mudança não altera a distribuição, mas eu não sou estatístico, então não posso garantir isso.

Comentados para aqueles menos familiarizados com o awk:

# Before the first line is read...
BEGIN { 
    # ...seed the random number generator.
    srand() 
}

# For each line:
# if the difference between the second and third columns is between 3 and 10 (exclusive)...
$3 - $2 > 3 &&
$3 - $2 < 10 &&
# ... with a probability of (total rows to select) / (total matching rows so far) ...
rand() < count / ++n {
    # ... If we haven't reached the number of rows we need, just add it to our list
    if (n <= count) {
        s[n] = $0 
    } else {
        # otherwise, replace a random entry in our list with the current line.
        s[1+int(rand()*count)] = $0 
    } 
} 

# After all lines have been processed...
END { 
    # Print all lines in our list.
    for (i in s) print s[i] 
}
    
por 22.06.2017 / 23:23
2

Aqui está uma maneira de fazer isso no GNU awk (que suporta rotinas de classificação personalizadas):

#!/usr/bin/gawk -f

function mycmp(ia, va, ib, vb) {
  return rand() < 0.5 ? 0 : 1;
}

BEGIN {
  srand();
}

$3 - $2 > 3 && $3 - $2 < 10 {
  a[NR]=$0;
} 

END {
  asort(a, b, "mycmp");
  for (i = 1; i < 3; i++) print b[i];
}

Teste com os dados fornecidos:

$ for i in {1..6}; do printf 'Try %d:\n' $i; ../randsel.awk file; sleep 2; done
Try 1:
g    1    8
e    6    14
Try 2:
a    1    5
b    4    12
Try 3:
b    4    12
a    1    5
Try 4:
e    6    14
a    1    5
Try 5:
b    4    12
a    1    5
Try 6:
e    6    14
b    4    12
    
por 23.06.2017 / 01:09
0

Publicando uma solução perl , pois não vejo nenhum motivo para que ela deva estar em awk (exceto para o desejo do OP):

#!/usr/bin/perl

use strict;
use warnings;
my $N = 2;
my $k;
my @r;

while(<>) {
    my @line = split(/\s+/);
    if ($line[2] - $line[1] > 3 && $line[2] - $line[1] < 10) {
        if(++$k <= $N) {
            push @r, $_;
        } elsif(rand(1) <= ($N/$k)) {
            $r[rand(@r)] = $_;
        }
    }
}

print @r;

Este é um exemplo clássico de amostragem de reservatório . O algoritmo foi copiado de aqui e modificado por mim para atender aos desejos específicos do OP .

Quando salvo no arquivo reservoir.pl , você o executa com ./reservoir.pl file1 file2 file3 ou cat file1 file2 file3 | ./reservoir.pl .

    
por 23.06.2017 / 13:00

Tags