AWK:条件を満たす任意のファイルラインを取得しますか?

AWK:条件を満たす任意のファイルラインを取得しますか?

条件を満たす任意の行セットを取得しようとしています。

たとえば、私のファイルが次のような場合:

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

次に、列3と列2の差が3より大きく10より小さい、正確に2つのランダムな行が必要です(たとえば、a、b、e、gで始まる行が適しています)。

この問題にどのように対処する必要がありますか?

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

答え1

これを行うこともできますが、awk行をランダムに選択するのは複雑で多くのコードが必要です。awkあなたの基準に一致する行を取得し、標準ツールを使用してランダムに選択するためにshuf使用します。

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

数回実行すると、ランダムに選択された行が表示されます。

$ 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
--

このshufツールはGNU coreutilsの一部であるため、ほとんどのLinuxシステムにデフォルトでインストールする必要があり、ほとんどの* nixで簡単に使用できます。

答え2

リストを一度だけ繰り返す純粋なawk回答が必要な場合:

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

読みやすくするためにファイルに保存されます。

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] 
}

アルゴリズムが若干異なりますクヌスアルゴリズムR;この変更は分布が変わらないと確信していますが、私は統計学者ではないため保証できません。

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] 
}

答え3

GNU awkでこれを行う1つの方法は次のとおりです(カスタムソートルーチンがサポートされています)。

#!/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];
}

与えられたデータでテスト:

$ 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

答え4

perlソリューションを含める理由がまったくないため、ソリューションを公開しますawk(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;

これは典型的な例です貯水池サンプリング。このアルゴリズムは以下からコピーされた。ここOPの特定の希望に合わせて私が修正しました。

ファイルに保存すると、reservoir.pl使用./reservoir.pl file1 file2 file3または実行できますcat file1 file2 file3 | ./reservoir.pl

関連情報