我已经看到了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
我觉得有必要以更短,更优雅的方式做到这一点,而不将整个文件的内容存储在内存中。在那儿?
除了最近读取的行和返回的随机行的当前候选者之外,您可以不存储任何内容。
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
Ruby Array类中已经内置了一个随机入口选择器:sample()。
def pick_random_line
File.readlines("data.txt").sample
end
此功能完全符合您的需要。
这不是一个单行。但它适用于任何大小的文本文件(零大小,可能:)。
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)的缺点:
这并不比你提出的要好得多,但至少它更短:
def pick_random_line
lines = File.readlines("data.txt")
lines[rand(lines.length)]
end
你可以做的一件事是让你的代码更加Rubyish省略大括号。使用readlines
和size
而不是readlines()
和size()
。
一个班轮:
def pick_random_line(file)
`head -$((${RANDOM} % `wc -l < #{file}` + 1)) #{file} | tail -1`
end
如果你抗议它不是Ruby,那么今年就去找一个名为Ruby的Euruko与香蕉不同。
PS:忽略SO不正确的语法高亮。
这是Mark的优秀答案的缩短版本,不像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
统计文件,在零和文件大小之间选择一个随机数,寻找文件中的那个字节。扫描到下一个换行符,然后读取并返回下一行(假设您不在文件的末尾)。