Phrogz Phrogz - 4 months ago 9
Ruby Question

Functionally find mapping of first value that passes a test

In Ruby, I have an array of simple values (possible encodings):

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


I want to keep reading a file from disk until the results are valid. I could do this:

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


...but of course this is dumb, since it reads the file twice for the good encoding. I could program it in gross procedural style like so:

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


But I want a functional solution. I could do it functionally like so:

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


…but of course that keeps reading files for every encoding, even if the first was valid.

Is there a simple pattern that is functional, but does not keep reading the file after a the first success is found?

Answer

If you sprinkle a lazy in there, map will only consume those elements of the array that are used by find - i.e. once find stops, map stops as well. So this will do what you want:

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