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

时间:2012-06-13 01:31:24

标签: ruby file io

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

我觉得必须能够以更短,更优雅的方式完成此操作,而无需将整个文件的内容存储在内存中。有吗?

7 个答案:

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

  1. 线越长,被挑选的几率就越大。

  2. 未考虑“\ r”行分隔符(特定于Windows)。使用具有Unix风格行结尾的文件!

答案 3 :(得分:2)

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

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

你可以做的一件事是让你的代码更多 Rubyish 省略大括号。使用readlinessize代替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)

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