我已经看到了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
我觉得必须能够以更短,更优雅的方式完成此操作,而无需将整个文件的内容存储在内存中。有吗?
答案 0 :(得分:36)
Ruby Array类中已经内置了一个随机条目选择器:sample()。
def pick_random_line
File.readlines("data.txt").sample
end
答案 1 :(得分:12)
除了最近读取的行和返回的随机行的当前候选者之外,您可以不存储任何内容。
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行,所以他们每人在第3行被选中的几率为1/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 :(得分:4)
此功能完全符合您的需要。
这不是一个单行。但它适用于任何大小的文本文件(除了零大小,可能是:)。
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”行分隔符(特定于Windows)。使用具有Unix风格行结尾的文件!
答案 3 :(得分:2)
这并不比你提出的要好得多,但至少它更短:
def pick_random_line
lines = File.readlines("data.txt")
lines[rand(lines.length)]
end
你可以做的一件事是让你的代码更多 Rubyish 省略大括号。使用readlines
和size
代替readlines()
和size()
。
答案 4 :(得分:0)
一个班轮:
def pick_random_line(file)
`head -$((${RANDOM} % `wc -l < #{file}` + 1)) #{file} | tail -1`
end
如果你抗议它不是Ruby,请在今年的Euruko中找到一个题为 Ruby不像香蕉的话题。
PS:忽略SO不正确的语法高亮。
答案 5 :(得分:0)
这是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
答案 6 :(得分:-1)
统计文件,在零和文件大小之间选择一个随机数,寻找文件中的那个字节。扫描到下一个换行符,然后读取并返回下一行(假设您不在文件的末尾)。