Ruby: Какой элегантный способ выбрать случайную строку из текстового файла?
Я видел некоторые действительно красивые примеры Ruby, и я пытаюсь переложить свое мышление, чтобы иметь возможность создавать их, а не просто восхищаться ими. Здесь лучшее, что я мог придумать для выбора случайной строки из файла:
def pick_random_line
random_line = nil
File.open("data.txt") do |file|
file_lines = file.readlines()
random_line = file_lines[Random.rand(0...file_lines.size())]
end
random_line
end
Мне кажется, что это возможно сделать короче, элегантнее, не сохраняя в памяти все содержимое файла. Есть?
Ответы
Ответ 1
Вы можете сделать это, не сохраняя ничего, кроме текущего кандидата для случайной строки.
def pick_random_line
chosen_line = nil
File.foreach("data.txt").each_with_index do |line, number|
chosen_line = line if rand < 1.0/(number+1)
end
return chosen_line
end
Итак, первая строка выбирается с вероятностью 1/1 = 1; вторая линия выбирается с вероятностью 1/2, поэтому половина времени она удерживает первую и половину времени, когда она переключается на вторую.
Затем третья строка выбирается с вероятностью 1/3 - таким образом, 1/3 от времени, когда она ее выбирает, а другая 2/3 времени она держит в зависимости от того, какая из первых двух она выбрала. Поскольку каждый из них имел 50% -ный шанс быть выбранным по строке 2, каждый из них заканчивается с 1/3 шансом быть выбранным по строке 3.
И так далее. На линии N каждая строка из 1-N имеет даже 1/N шанс быть выбранным, и она проходит весь путь через файл (пока файл не настолько огромен, что 1/(количество строк в файле ) меньше, чем epsilon:)). И вы делаете только один проход через файл и никогда не храните сразу несколько строк.
РЕДАКТИРОВАТЬ. Вы не получите настоящего краткого решения с этим алгоритмом, но вы можете превратить его в однострочный, если хотите:
def pick_random_line
File.foreach("data.txt").each_with_index.reduce(nil) { |picked,pair|
rand < 1.0/(1+pair[1]) ? pair[0] : picked }
end
Ответ 2
В класс Ruby Array встроен селектор случайных записей: sample().
def pick_random_line
File.readlines("data.txt").sample
end
Ответ 3
Эта функция выполняет именно то, что вам нужно.
Это не однострочный. Но он работает с текстовыми файлами любого (кроме нулевого размера, может быть:).
def random_line(filename)
blocksize, line = 1024, ""
File.open(filename) do |file|
initial_position = rand(File.size(filename)-1)+1 # random pointer position. Not a line number!
pos = Array.new(2).fill( initial_position ) # array [prev_position, current_position]
# Find beginning of current line
begin
pos.push([pos[1]-blocksize, 0].max).shift # calc new position
file.pos = pos[1] # move pointer backward within file
offset = (n = file.read(pos[0] - pos[1]).rindex(/\n/) ) ? n+1 : nil
end until pos[1] == 0 || offset
file.pos = pos[1] + offset.to_i
# Collect line text till the end
begin
data = file.read(blocksize)
line.concat((p = data.index(/\n/)) ? data[0,p.to_i] : data)
end until file.eof? or p
end
line
end
Попробуйте:
filename = "huge_text_file.txt"
100.times { puts random_line(filename).force_encoding("UTF-8") }
Незначительные (imho) недостатки:
-
Чем длиннее строка, тем выше вероятность того, что она будет выбрана.
-
не учитывает разделитель строк "\ r" (зависит от окна). Используйте файлы с окончанием строки в стиле Unix!
Ответ 4
Это не намного лучше, чем вы придумали, но, по крайней мере, короче:
def pick_random_line
lines = File.readlines("data.txt")
lines[rand(lines.length)]
end
Одна вещь, которую вы можете сделать, чтобы сделать свой код более Rubyish, не позволяет скобки. Используйте readlines
и size
вместо readlines()
и size()
.
Ответ 5
Один вкладыш:
def pick_random_line(file)
`head -$((${RANDOM} % `wc -l < #{file}` + 1)) #{file} | tail -1`
end
Если вы протестуете против того, что это не Руби, найдите разговор в этом году. Euruko под названием Ruby отличается от банана.
PS: игнорировать неправильную подсветку синтаксиса SO.
Ответ 6
Здесь более короткая версия Mark exellent ответ, не такая короткая, как Dave, хотя
def pick_random_line number=1, chosen_line=""
File.foreach("data.txt") {|line| chosen_line = line if rand < 1.0/number+=1}
chosen_line
end
Ответ 7
Статируйте файл, выберите случайное число между нулем и размером файла, ищите этот байт в файле. Сканируйте до следующей новой строки, затем прочитайте и верните следующую строку (если вы не находитесь в конце файла).