it-swarm.com.ru

Случайно выбрать строки из файла, не обрезая его с 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
51
neversaint

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

Во втором случае просто рандомизируйте по 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 
52
Bill

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

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

Исходная тема: Отправлено для вашего обзора - случайная выборка в 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" найдут множество реализаций. Здесь - это код Perl и Python, который реализует то, что вы хотите, а здесь - еще один поток переполнения стека, обсуждающий это.

5
Tudor Bosman

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

5
advait

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

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

Пример на питоне:

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

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