优雅的方式在Ruby中逐行按字母顺序合并2个文本文件

时间:2014-01-07 11:01:21

标签: ruby file-io

我有2个日志文件,它们都包含以时间顺序从日期戳开始的文本行。我想将两个文件合并到一个文件中,该文件包含按时间顺序合并的两个文件中的所有行。由于日期戳的布局,在这种情况下按时间顺序排列与字母相同。

我编写了一个执行此操作的Ruby脚本并且工作正常,但我是Ruby新手只是学习语言而且我真正喜欢Ruby的一件事就是语法糖和代码的可读性。而且我不禁觉得我的解决方案非常笨重,而且肯定有更好的方法可以解决同样的问题。我并不需要在算法上更好地寻找,但在句法上。 Ruby似乎对几乎所有东西都有一个单线解决方案,所以也许是这样的问题。

if ARGV.length != 2
    puts "Wrong number of arguments. Expected 2 arguments (path to 2 log files to be merged)"
end

merged_file = File.open("merge_out.txt", "w")
file1 = File.open(ARGV[0], "r")
file2 = File.open(ARGV[1], "r")

line1 = file1.gets
line2 = file2.gets

while (line1 != nil or line2 !=nil)
    if line1 == nil
        # no more line1 so write line2 and proceed file2
        merged_file.puts line2
        line2 = file2.gets        
    elsif line2 == nil
        # no more line2 so write line1 and proceed file1
        merged_file.puts line1
       line1 = file1.gets          
    else 
        comp = line1<=>line2
        #both lines present, write and proceed the (alphabetically) smaller one 
        #as this is the one with the earlier time stamp
        if comp == -1
            merged_file.puts line1
            line1 = file1.gets              
        else
            merged_file.puts line2
            line2 = file2.gets           
         end    
    end       
end

那么,如何更优雅地完成这项工作?

5 个答案:

答案 0 :(得分:4)

有时添加维度会使解决方案更漂亮。从本质上讲,将file1,file2变量转换为数组[ file1, file2 ],这会打开很多Ruby Array语法,执行您已编码到初始解决方案中的测试。

if ARGV.length < 2
    puts "Wrong number of arguments. Expected 2 or more files to merge."
end

merged_file = File.open("merge_out.txt", "w")

files = ARGV.map { |filename| File.open( filename, "r") }

lines = files.map { |file| file.gets }

while lines.any?
    next_line = lines.compact.min
    file_id = lines.index( next_line )
    merged_file.print next_line
    lines[ file_id ] = files[ file_id ].gets
end

因此,这不仅更短,而且副作用可以同时处理更多输入文件。虽然如果你不需要,只需改回第一次检查。

答案 1 :(得分:3)

我知道这不是红宝石,但对于这类问题,一个简单的bash命令可以完成这项工作:

cat file1.dat file2.dat | sort > out.dat

如果你真的希望ruby用一小段代码完成工作:

File.open('out.dat','w') do |f|
 f.puts (File.read('file1.dat') << File.read('file2.dat') ).split("\n").sort
end

请注意,这并未考虑您的数据已经部分排序的事实。它简洁,但不一定是最有效的方法。

答案 2 :(得分:2)

你可以将你的4个案例合并为两个案例:只要问问自己,line1是否是第一行:

if line1 && line1 < line2 
    …

答案 3 :(得分:2)

这不是ruby语法,但在算法上,您可以将每个文件读入数组或列表,然后对数组/列表进行排序,然后打印它。使用内置进行排序看起来更好看。根据内置的实现,它可能比您编写的任何代码都快。我想如果它成为一个问题你只会担心性能:)

在python中:

# create a list with all the first file in it as elements
with open(file1) as f:
    content = f.readlines()   

# add the second file contents to the list    
with open(file1) as f
    content = content + f.readlines()    # list catenation

content.sort()    # sort the list

# join all the lines in the list together into a string an print them
outfile.write(''.join(content))   

由于你是一个红宝石新手,找到相同的红宝石内置将是一个很好的学习练习:)

请注意,如果文件很大,这不是一个好主意,因为它将整个文件吸入内存。再次 - 简单代码的代价。如果你有很多文件,你需要一些更笨重的代码来处理它们:)

编辑:

我用Google搜索了一些红宝石。 出现

# google "read file into array ruby"
array = IO.readlines file1_pathname + IO.readlines file2_pathname

# google "sort array ruby
array.sort

# google "print array ruby"
puts array.inspect

是那种事。

HTH

答案 4 :(得分:1)

NeilSlater对我的另一个答案的评论使我的思绪继续“所以,如果你不能啜饮,那又怎样?”

这个怎么样:

line1 = file1.gets
line2 = file2.gets

while (line1 && line2)
    while(line1.to_s >= line2.to_s)  # to_s to protect against nil
        merged_file.puts line2
        line2 = file2.gets
    end        
    while(line2.to_s > line1.to_s)
        merged_file.puts line1
        line1 = file1.gets          
   end
end
while(line1)
   merged_file.puts line1
   line1 = file1.gets          
end
while(line2)
   merged_file.puts line2
   line2 = file2.gets      
end

它不是很短,并没有使用任何语法魔法或方便的内置,但至少它更规则......