Случайно выбирайте строки из файла без разрыва с 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"