Took around a 100 seconds to understand. Would be twice faster if I had a commentary: # this code merges two presorted files
.
All is pretty ok, but what you forgot here is to close all opened files. And it is the only reason to have a first variable declared, otherwise I would mix both map
s together.
If you pass filenames via ARGV, there should be also the merged_filename
as the first or last parameter. Or at any position and optional if you start to use some named options parsing.
So we have this:
files_to_merge = ARGV.map &File.method(:open)
lines = files_to_merge.map &:gets
merged_file = File.open "merge_out.txt", "w"
while lines.any?
merged_file.print(next_line = lines.compact.min)
file_id = lines.index next_line
lines[file_id] = files_to_merge[file_id].gets
end
merged_file.close
files_to_merge.each &:close
But what if try to solve the problem of mindblowing file_id = .index
?
You could put file handler and current line together into an Array or Hash, but I don't feel like it makes code better:
files_and_lines = ARGV.map(&File.method(:open)).map{ |file| {file:file, line:file.gets} }
...
loop do
files_and_lines.select!{ |tuple| tuple[:line] }
break if files_and_lines.empty?
next_tuple = files_and_lines.min_by{ |tuple| tuple[:line] }
merged_file.print next_tuple[:line]
next_tuple[:line] = next_tuple[:file].gets
end
...
Trying to make it shorter didn't work for me: converting tuples into Hash pair file->line
can't easily return file for line and line->file
doesn't look better either because to edit a key you have to have some temporal array
variable:
files_and_lines = Hash[ ARGV.map(&File.method(:open)).map{ |file| [file.gets, file] } ]
...
loop do
break if files_and_lines.keep_if{ |line, file| line }.empty?
array = files_and_lines.to_a.sort_by &:first
merged_file.print array[0][0]
array[0][0] = array[0][1].gets
files_and_lines = Hash[array]
end
...
So just leave Britney alone don't touch it -- .index
is ok.