web-dev-qa-db-ja.com

Unixで丸lurみせずにファイルから行をランダムに選択する

10 ^ 7行のファイルがあり、そのファイルから1/100行をランダムに選択します。これは私が持っているAWKコードですが、すべてのファイルの内容を事前に丸lurみします。私のPCのメモリは、そのような音を処理できません。それを行う他のアプローチはありますか?

awk 'BEGIN{srand()}
!/^$/{ a[c++]=$0}
END {  
  for ( i=1;i<=c ;i++ )  { 
    num=int(Rand() * c)
    if ( a[num] ) {
        print a[num]
        delete a[num]
        d++
    }
    if ( d == c/100 ) break
  }
 }' file
51
neversaint

その数の行がある場合、exactly 1%が必要ですか、それとも統計的な推定で十分ですか?

その2番目のケースでは、各行で1%だけランダム化します...

awk 'BEGIN {srand()} !/^$/ { if (Rand() <= .01) print $0}'

ヘッダー行とその後のランダムな行のサンプルが必要な場合は、次を使用します。

awk 'BEGIN {srand()} !/^$/ { if (Rand() <= .01 || FNR==1) print $0}'
86
cadrian

あなたはawkを使用しましたが、それが必要かどうかはわかりません。そうでない場合、Perlを使用して(ファイル全体をメモリにロードせずに)簡単な方法を以下に示します。

cat your_file.txt | Perl -n -e 'print if (Rand() < .01)'

(コメントからの簡単なフォーム):

Perl -ne 'print if (Rand() < .01)' your_file.txt 
53
Bill

私はこの正確なコードをGawkで書いた-あなたは幸運だ。入力順序が保持されるため、部分的に長くなります。おそらく、実行可能なパフォーマンスの強化があります。

このアルゴリズムは、事前に入力サイズを知らなくても正しいです。私は rosetta stone を投稿しました。 (不必要な比較を行うため、このバージョンは投稿しませんでした。)

元のスレッド: レビュー用に提出-awkでのランダムサンプリング

# Waterman's Algorithm R for random sampling
# by way of Knuth's The Art of Computer Programming, volume 2

BEGIN {
    if (!n) {
        print "Usage: sample.awk -v n=[size]"
        exit
    }
    t = n
    srand()

}

NR <= n {
    pool[NR] = $0
    places[NR] = NR
    next

}

NR > n {
    t++
    M = int(Rand()*t) + 1
    if (M <= n) {
        READ_NEXT_RECORD(M)
    }

}

END {
    if (NR < n) {
        print "sample.awk: Not enough records for sample" \
            > "/dev/stderr"
        exit
    }
    # gawk needs a numeric sort function
    # since it doesn't have one, zero-pad and sort alphabetically
    pad = length(NR)
    for (i in pool) {
        new_index = sprintf("%0" pad "d", i)
        newpool[new_index] = pool[i]
    }
    x = asorti(newpool, ordered)
    for (i = 1; i <= x; i++)
        print newpool[ordered[i]]

}

function READ_NEXT_RECORD(idx) {
    rec = places[idx]
    delete pool[rec]
    pool[NR] = $0
    places[idx] = NR  
} 
19
Steven Huwig

これは、ほとんどのGNU/Linuxマシンで動作するはずです。

$ shuf -n $(( $(wc -l < $file) / 100)) $file

メモリ管理がGNU shufコマンドによって不適切に行われた場合、私は驚くでしょう。

16
ashawley

(サイズが不明な)大規模な母集団からN個の要素を均一にサンプリングする方法の問題は、 Reservoir Sampling として知られています。 (アルゴリズムの問​​題が好きな場合は、Wikipediaのアルゴリズムを読まずに問題を解決するために数分費やしてください。)

「リザーバーサンプリング」のWeb検索では、多くの実装が見つかります。 ここ はPerlで、Python必要なものを実装するコード)、および ここ はそれを議論する別のStack Overflowスレッドです。

5
Tudor Bosman

awkはわかりませんが、説明した問題のより一般的なバージョンを解決するための優れた手法があります。 Rand <0.01の場合、ファイル内の行を返す行アプローチよりもはるかに高速であるため、上記のような(数千、数百万)回のようなタスクを実行する場合に役立ちます。 リザーバーサンプリング および このページ として知られているのは、状況に適用できるバージョンのかなり良い説明です。

5
advait

次の2つのパスで実行できます。

  • 何行あるかを数えるためだけに、ファイルを1回実行します
  • 印刷したい行の行番号をランダムに選択し、ソートされたリスト(またはセット)に保存します
  • ファイルをもう一度実行し、選択した位置の行を選択します

Pythonの例:

fn = '/usr/share/dict/words'

from random import randint
from sys import stdout

count = 0
with open(fn) as f:
   for line in f:
      count += 1

selected = set()
while len(selected) < count//100:
   selected.add(randint(0, count-1))

index = 0
with open(fn) as f:
   for line in f:
      if index in selected:
          stdout.write(line)
      index += 1
3
sth

この場合、正確にk値を取得するためのリザーバーサンプリングはawkで十分に簡単なので、解決策がまだ示唆されていないことに驚いています。同じ問題を解決しなければならなかったので、サンプリング用に次のawkプログラムを作成しました。

_NR < k {
    reservoir[NR] = $0;
}
NR >= k {
    i = int(NR * Rand());
    if (i < k) {
        reservoir[i] = $0;
    }
}
END {
    for (i in reservoir) {
        print reservoir[i];
    }
}
_

それからkが何であるかを理解することは、例えばawk -v 'k=int('$(dc -e "$(cat FILE | wc -l) 0.01 * n")')'を設定することによって、別々に行われなければなりません

2
kqr

1%の行をランダムに選択するために最後まで待つのではなく、「/ ^ $ /」で100行ごとに実行します。そうすれば、一度に100行しか保持できません。

1
Travis Jensen

メモリの枯渇を避けることだけが目的であり、ファイルが通常のファイルである場合、リザーバーサンプリングを実装する必要はありません。ファイル内の行数は、ファイルで2つのパスを実行する場合に知ることができます。1つは行数を取得するためです(wc -l)、サンプルを選択するもの:

file=/some/file
awk -v percent=0.01 -v n="$(wc -l < "$file")" '
  BEGIN {srand(); p = int(n * percent)}
  Rand() * n-- < p {p--; print}' < "$file"
1