Случайно выбирайте строки из файла без разрыва с Unix

У меня есть файл 10 ^ 7 строк, в котором я хочу выбрать 1/100 строк в случайном порядке из файла. Это код AWK, который у меня есть, но он перекрывает все содержимое файла перед рукой. Память моего ПК не может справиться с такими прерываниями. Есть ли другой подход к этому?

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

Ответ 1

Если у вас есть много строк, вы уверены, что хотите точно 1% или статистической оценки будет достаточно?

В этом втором случае просто рандомизируйте по 1% в каждой строке...

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

Если вы хотите, чтобы строка заголовка плюс случайный образец строк после, используйте:

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

Ответ 2

Вы использовали awk, но я не знаю, требуется ли это. Если это не так, здесь тривиальный способ сделать w/perl (и без загрузки всего файла в память):

cat your_file.txt | perl -n -e 'print if (rand() < .01)'

(более простая форма, из комментариев):

perl -ne 'print if (rand() < .01)' your_file.txt 

Ответ 3

Я написал этот точный код в Gawk - вам повезло. Это длительное время, потому что оно сохраняет порядок ввода. Возможно, есть улучшения производительности.

Этот алгоритм корректен, не зная заранее размер ввода. Я разместил здесь розетта камень. (Я не публиковал эту версию, потому что она делает ненужные сравнения.)

Оригинальная тема: Представлено для вашего обзора - случайная выборка в awk.

# Waterman Algorithm R for random sampling
# by way of Knuth 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  
} 

Ответ 4

Это должно работать на большинстве машин GNU/Linux.

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

Я был бы удивлен, если управление памятью было сделано ненадлежащим образом командой GNU shuf.

Ответ 5

Я не знаю awk, но есть отличная методика для решения более общей версии описанной вами проблемы, и в общем случае она намного быстрее, чем для строки в строке возврата файла, если rand < 0,01, поэтому было бы полезно, если вы намереваетесь выполнять такие задачи, как вышеупомянутое много (тысячи, миллионы) раз. Он известен как выборки коллектора и эта страница имеет довольно хорошее объяснение версии, которая применима к вашей ситуации.

Ответ 6

Проблема того, как равномерно отбирать N элементов из большой совокупности (неизвестного размера), называется выборки коллектора. (Если вам нравятся проблемы с алгоритмами, проведите несколько минут, пытаясь решить проблему, не читая алгоритм в Википедии.)

Веб-поиск "Выборки коллектора" найдет много реализаций. Здесь - это код Perl и Python, который реализует то, что вы хотите, и здесь является другим Qaru обсуждает его.

Ответ 7

Вы можете сделать это за два прохода:

  • Пропустите файл один раз, просто чтобы подсчитать, сколько строк есть
  • Произвольно выбирайте номера строк строк, которые вы хотите распечатать, сохраняя их в отсортированном списке (или наборе)
  • Запустите файл еще раз и выделите строки в выбранных позициях

Пример в 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

Ответ 8

Вместо того, чтобы дождаться окончания случайного выбора ваших 1% строк, сделайте это каждые 100 строк в "/^ $/". Таким образом, вы сохраняете только 100 строк за раз.

Ответ 9

Если цель состоит в том, чтобы избежать исчерпания памяти, и файл является обычным файлом, нет необходимости внедрять выборку коллектора. Количество строк в файле может быть известно, если вы делаете два прохода в файле, один для получения количества строк (например, с помощью 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"