13

我已经看到了一些非常漂亮的 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 

我觉得必须有可能以更短、更优雅的方式做到这一点,而无需将整个文件的内容存储在内存中。有没有?

4

7 回答 7

38

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

def pick_random_line
  File.readlines("data.txt").sample
end
于 2012-06-13T02:31:07.833 回答
14

除了最近读取的行和返回的随机行的当前候选行之外,您可以在不存储任何内容的情况下执行此操作。

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
于 2012-06-13T02:06:16.697 回答
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") }

微不足道的(恕我直言)缺点:

  1. 线越长,被选中的机会就越高。

  2. 不考虑“\r”行分隔符(windows-specific)。使用带有 Unix 风格行尾的文件!

于 2013-02-17T09:27:38.207 回答
2

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

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

你可以做的一件事是让你的代码更加Rubyish就是省略大括号。使用readlinesandsize代替readlines()and size()

于 2012-06-13T01:45:38.533 回答
0

一个班轮:

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

如果您抗议它不是 Ruby,请在今年的 Euruko 中找到一个名为Ruby is like a Banana的演讲。

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

于 2012-06-13T03:05:44.197 回答
0

这是马克出色答案的较短版本,虽然不如戴夫的短

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
于 2012-06-13T11:52:51.987 回答
-1

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

于 2012-06-13T02:51:13.767 回答