Подтвердить что ты не робот

Как случайным образом удалить несколько строк из большого файла?

У меня есть большой текстовый файл размером 13 ГБ с 158 609 739 строк, и я хочу случайным образом выбрать 155 000 000 строк.

Я попытался скрестить файл, а затем разрезал первые строки 155000000, но похоже, что моя память RAM (16 ГБ) недостаточно велика для этого. Попытки трубопровода:

shuf file | head -n 155000000
sort -R file | head -n 155000000

Теперь вместо того, чтобы выбирать строки, я думаю, что больше памяти эффективно удаляет 3,609,739 случайных строк из файла, чтобы получить окончательный файл из 155000000 строк.

4b9b3361

Ответ 1

При копировании каждой строки файла на выход оцените его вероятность того, что он должен быть удален. Первая строка должна иметь шанс на удаление 3,609,739/158,609,739. Если вы создаете случайное число от 0 до 1, и это число меньше этого отношения, не копируйте его на выход. Теперь шансы на вторую линию составляют 3,609,738/158,609,738; если эта строка не удалена, коэффициенты для третьей строки составляют 3,609,738/158,609,737. Повторяйте до конца.

Поскольку коэффициенты изменяются при обработке каждой строки, этот алгоритм гарантирует точное количество строк. После того, как вы удалили 3,609,739, шансы равны нулю; если в любой момент вам нужно будет удалить каждую оставшуюся строку в файле, шансы перейдут к одному.

Ответ 2

Вы всегда можете заранее генерировать номера строк (список из 3 609 739 случайных чисел, выбранных без замены), которые вы планируете удалить, а затем просто перебирать файл и копировать в другой, пропуская строки по мере необходимости. Если у вас есть место для нового файла, это сработает.

Вы можете выбрать случайные числа с random.sample Например.

random.sample(xrange(158609739), 3609739)

Ответ 3

Доказательство ответа Ransom Mark

Пусть проще использовать номера (по крайней мере для меня!):

  • 10 элементов
  • удалить 3 из них

В первый раз через цикл мы предположим, что первые три элемента удаляются - вот как выглядят вероятности:

  • первый элемент: 3/10 = 30%
  • второй элемент: 2/9 = 22%
  • третий элемент: 1/8 = 12%
  • четвертый элемент: 0/7 = 0%
  • пятый элемент: 0/6 = 0%
  • шестой элемент: 0/5 = 0%
  • седьмой элемент: 0/4 = 0%
  • восьмой элемент: 0/3 = 0%
  • девятый элемент: 0/2 = 0%
  • Десятый элемент: 0/1 = 0%

Как вы можете видеть, когда он достигает нуля, он остается равным нулю. Но что, если ничего не удаляется?

  • первый элемент: 3/10 = 30%
  • второй элемент: 3/9 = 33%
  • третий элемент: 3/8 = 38%
  • четвертый элемент: 3/7 = 43%
  • пятый элемент: 3/6 = 50%
  • шестой элемент: 3/5 = 60%
  • седьмой элемент: 3/4 = 75%
  • восьмой элемент: 3/3 = 100%
  • девятый элемент: 2/2 = 100%
  • десятый элемент: 1/1 = 100%

Таким образом, хотя вероятность зависит от каждой строки, в целом вы получаете результаты, которые вы ищете. Я сделал еще один шаг и закодировал тест на Python на миллион итераций в качестве последнего доказательства для себя - удалите семь элементов из списка из 100:

# python 3.2
from __future__ import division
from stats import mean  # http://pypi.python.org/pypi/stats
import random

counts = dict()
for i in range(100):
    counts[i] = 0

removed_failed = 0

for _ in range(1000000):
    to_remove = 7
    from_list = list(range(100))
    removed = 0
    while from_list:
        current = from_list.pop()
        probability = to_remove / (len(from_list) + 1)
        if random.random() < probability:
            removed += 1
            to_remove -= 1
            counts[current] += 1
    if removed != 7:
        removed_failed += 1

print(counts[0], counts[1], counts[2], '...',
      counts[49], counts[50], counts[51], '...',
      counts[97], counts[98], counts[99])
print("remove failed: ", removed_failed)
print("min: ", min(counts.values()))
print("max: ", max(counts.values()))
print("mean: ", mean(counts.values()))

и здесь результаты одного из нескольких раз я его запускал (все они были похожи):

70125 69667 70081 ... 70038 70085 70121 ... 70047 70040 70170
remove failed:  0
min:  69332
max:  70599
mean:  70000.0

Последнее замечание: Python random.random() - [0.0, 1.0) (не включает в себя 1.0 как возможность).

Ответ 4

Я считаю, что вы ищете "Алгоритм S" из раздела 3.4.2 Кнута (DE Knuth, The Art of Computer Programming) Том 2: Семинумерический Алгоритмы, второе издание. Addison-Wesley, 1981).

Вы можете увидеть несколько реализаций в http://rosettacode.org/wiki/Knuth%27s_algorithm_S

В списке Perlmonks есть некоторые реализации Perl алгоритма S и алгоритма R, которые также могут оказаться полезными.

Эти алгоритмы полагаются на содержательную интерпретацию чисел с плавающей запятой, таких как 3609739/158609739, 3609738/158609738 и т.д., которые могут не иметь достаточного разрешения со стандартным типом Float, если только тип данных Float не реализован с использованием номера двойная точность или больше.

Ответ 5

Здесь возможно решение с использованием Python:

import random

skipping = random.sample(range(158609739), 3609739)

input = open(input)
output = open(output, 'w')

for i, line in enumerate(input):
    if i in skipping:
        continue
    output.write(line)

input.close()
output.close()

Здесь другой метод Mark:

import random

lines_in_file = 158609739
lines_left_in_file = lines_in_file
lines_to_delete = lines_in_file - 155000000

input = open(input)
output = open(output, 'w')

try:
    for line in input:
        current_probability = lines_to_delete / lines_left_in_file
        lines_left_in_file -= 1
        if random.random < current_probability:
            lines_to_delete -= 1
            continue
        output.write(line)
except ZeroDivisionError:
    print("More than %d lines in the file" % lines_in_file)
finally:
    input.close()
    output.close()

Ответ 6

Я написал этот код, прежде чем увидеть, что Даррен Инь выразил свой принцип.

Я изменил свой код, чтобы использовать имя skipping (я не решался выбрать kangaroo...) и ключевое слово continue из Этана Фурмана, чей кодовый принцип тоже.

Я определил аргументы по умолчанию для параметров функции, чтобы функция могла использоваться несколько раз без необходимости повторной привязки при каждом вызове.

import random
import os.path

def spurt(ff,skipping):
    for i,line in enumerate(ff):
        if i in skipping:
            print 'line %d excluded : %r' % (i,line)
            continue
        yield line

def randomly_reduce_file(filepath,nk = None,
                         d = {0:'st',1:'nd',2:'rd',3:'th'},spurt = spurt,
                         sample = random.sample,splitext = os.path.splitext):

    # count of the lines of the original file
    with open(filepath) as f:  nl = sum(1 for _ in f)

    # asking for the number of lines to keep, if not given as argument
    if nk is None:
        nk = int(raw_input('  The file has %d lines.'
                           '  How many of them do you '
                           'want to randomly keep ? : ' % nl))

    # transfer of the lines to keep,
    # from one file to another file with different name
    if nk<nl:
        with open(filepath,'rb') as f,\
             open('COPY'.join(splitext(filepath)),'wb') as g:
            g.writelines(  spurt(f,sample(xrange(0,nl),nl-nk) )  )
            # sample(xrange(0,nl),nl-nk) is the list
            # of the counting numbers of the lines to be excluded 
    else:
        print '   %d is %s than the number of lines (%d) in the file\n'\
              '   no operation has been performed'\
              % (nk,'the same' if nk==nl else 'greater',nl)

Ответ 7

С переменной $RANDOM вы можете получить случайное число от 0 до 32 767.

С этим вы можете читать в каждой строке и видеть, меньше ли $RANDOM меньше 155,000,000 / 158,609,739 * 32,767 (это 32 021), и если да, пусть строка проходит.

Конечно, это не даст вам ровно 150 000 000 строк, но довольно близко к нему в зависимости от нормальности генератора случайных чисел.

РЕДАКТИРОВАТЬ: Ниже приведен код для запуска:

#!/bin/bash
while read line; do
  if (( $RANDOM < 32021 ))
  then
    echo $line
  fi
done

Назовите его так:

thatScript.sh <inFile.txt >outFile.txt