从没有重复的大文件中选择一个随机行

时间:2013-04-14 18:57:55

标签: ruby file-io

我正在尝试从大文件(>百万行)中选择一个随机行,而不是选择任何重复项。如果有一个骗局,那么我想继续挑选,直到找到一个非欺骗。

到目前为止我得到了什么:

@already_picked = []

def random_line
  chosen_line = nil
  chosen_line_number = nil
  File.foreach("OSPD4.txt").each_with_index do |line, number| 
    if rand < 1.0/(number+1)
      chosen_line_number = number
      chosen_line = line
    end
  end
  chosen_line
  if @already_picked.include(chosen_line_number)?
    # what here?
  else
    @already_picked << chosen_line_number
  end
end

100.times do |t|
  random_line
end

我不确定在if子句

中该怎么做

4 个答案:

答案 0 :(得分:2)

100万行不是很多。如果它们平均100字节/行,则内存为100MB。这么简单,继续前进

File.readlines("file").sample(100)

如果你开始说话大于容易适合内存,那么下一步是对文件进行一次传递以记录行位置,然后从中提取样本。

class RandomLine
  def initialize(fn)
    @file = File.open(fn,'r')
    @positions = @file.lines.inject([0]) { |m,l| m << m.last + l.size }.shuffle
  end

  def pick
    @file.seek(@positions.pop)
    @file.gets
  end
end

答案 1 :(得分:1)

每次请求随机行时,您的方法都可能会读取大量文件。更好的方法可能是读取整个文件一次并构建一个表格,其中每行开始(这样您就不必将所有数据保存在内存中)。假设文件没有改变,那么你可以在这个表中寻找一个随机位置并读取一行。快点。一种可能的实现方式:

class RandomLine
  def initialize(filename)
    @file = File.open(filename)
    @table = [0]
    @picked = []
    File.foreach(filename) do |line|
      @table << @table.last + line.size
    end
  end
  def pick
    return nil if @table.size == 0 # if no more lines, nil
    i = rand(@table.size) # random line
    @file.seek(@table[i]) # go to the line
    @table.delete_at(i)   # remove from the table
    line = @file.readline
    if @picked.include? line
      pick   # pick another line
    else
      @picked << line
      line
    end
  end
end

用法:

random_line = RandomLine.new("OSPD4.txt")
100.times do
  puts random_line.pick
end

答案 2 :(得分:1)

虽然为了避免将文件读入内存而花费大量工作是非常高尚的,但是数百万行并不是那么多。另一种方法是尝试一个简单的解决方案,只有在实践中实际上很慢时才会变得复杂。

class RandomLine
  def initialize fn
    open(fn, 'r') { |f| @i, @lines = -1, f.readlines.shuffle }
  end

  def pick
    @lines[@i += 1]
  end
end

q = o = RandomLine.new '/etc/hosts'
puts q while q = o.pick

答案 3 :(得分:1)

当读取文件返回行数组时,您可以使用#sample方法。

File.readlines("OSPD4.txt").sample(100).map{|line| line.chomp }
# using chomp to get rid of EOL