AWK: 조건을 충족하는 임의의 파일 라인을 얻으시겠습니까?

AWK: 조건을 충족하는 임의의 파일 라인을 얻으시겠습니까?

조건을 충족하는 임의의 줄 집합을 얻으려고 합니다.

예를 들어, 내 파일이 다음과 같은 경우:

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

그런 다음 열 3과 열 2의 차이가 3보다 크고 10보다 작은 정확히 두 개의 임의 행을 원합니다(예: 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에서 이를 수행하는 한 가지 방법은 다음과 같습니다(사용자 정의 정렬 루틴이 지원됨).

#!/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.

관련 정보