在功能上查找通过测试的第一个值的映射

时间:2013-07-11 22:29:11

标签: ruby functional-programming

在Ruby中,我有一组简单的值(可能的编码):

encodings = %w[ utf-8 iso-8859-1 macroman ]

我想继续从磁盘读取文件,直到结果有效。我能做到这一点:

good = encodings.find{ |enc| IO.read(file, "r:#{enc}").valid_encoding? }
contents = IO.read(file, "r:#{good}")

...但当然这很愚蠢,因为它为良好的编码读取了两次文件。我可以用粗略的程序风格来编程,如下所示:

contents = nil
encodings.each do |enc|
  if (s=IO.read(file, "r:#{enc}")).valid_encoding?
    contents = s
    break
  end
end

但我想要一个功能性解决方案。我可以在功能上这样做:

contents = encodings.map{|e| IO.read(f, "r:#{e}")}.find{|s| s.valid_encoding? }

...但当然,即使第一个编码有效,也会继续读取每个编码的文件。

是否有一个功能简单的模式,但是在找到第一个成功后却没有继续读取文件?

4 个答案:

答案 0 :(得分:4)

如果你在那里撒lazymap只会消耗find使用的数组元素 - 即find停止map也停止了。所以这将做你想要的:

possible_reads = encodings.lazy.map {|e| IO.read(f, "r:#{e}")}
contents = possible_reads.find {|s| s.valid_encoding? }

答案 1 :(得分:1)

跳到sepp2k的回答:如果你不能使用2.0,懒人的枚举可以很容易地在1.9中实现:

class Enumerator

  def lazy_find
    self.class.new do |yielder|
      self.each do |element|
        if yield(element)
          yielder.yield(element)
          break
        end
      end
    end
  end

end

a = (1..100).to_enum
p a.lazy_find { |i| i.even? }.first

# => 2

答案 2 :(得分:1)

答案 3 :(得分:0)

我能想到的最好的是与我们的好朋友inject

contents = encodings.inject(nil) do |s,enc|
  s || (c=File.open(f,"r:#{enc}").valid_encoding? && c
end

这仍然是次优的,因为它在找到匹配后继续循环编码,虽然它对它们没有任何作用,所以这是一个小丑。大多数丑陋来自......好吧,代码本身。 :/