Ruby:从文本文件中选择随机行的优雅方法是什么?

问题描述 投票:13回答:7

我已经看到了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 

我觉得有必要以更短,更优雅的方式做到这一点,而不将整个文件的内容存储在内存中。在那儿?

ruby file io
7个回答
13
投票

除了最近读取的行和返回的随机行的当前候选者之外,您可以不存储任何内容。

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

36
投票

Ruby Array类中已经内置了一个随机入口选择器:sample()。

def pick_random_line
  File.readlines("data.txt").sample
end

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)的缺点:

  1. 线越长,它被挑选的机会就越大。
  2. 没有考虑“\ r”行分隔符(特定于Windows)。使用带有Unix风格的行结尾的文件!

2
投票

这并不比你提出的要好得多,但至少它更短:

def pick_random_line
  lines = File.readlines("data.txt")
  lines[rand(lines.length)]
end

你可以做的一件事是让你的代码更加Rubyish省略大括号。使用readlinessize而不是readlines()size()


0
投票

一个班轮:

def pick_random_line(file)
  `head -$((${RANDOM} % `wc -l < #{file}` + 1)) #{file} | tail -1`
end

如果你抗议它不是Ruby,那么今年就去找一个名为Ruby的Euruko与香蕉不同。

PS:忽略SO不正确的语法高亮。


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

-1
投票

统计文件,在零和文件大小之间选择一个随机数,寻找文件中的那个字节。扫描到下一个换行符,然后读取并返回下一行(假设您不在文件的末尾)。

© www.soinside.com 2019 - 2024. All rights reserved.