Ruby:解析CSV并循环遍历行的问题

时间:2013-11-20 12:59:13

标签: ruby regex parsing csv

我有一个包含多个文件名和日期的CSV:

"doc_1.doc", "date1"
"doc_2.doc", "date2"
"doc_5.doc", "date5"

问题在于文件编号之间存在许多空白,例如:doc_2doc_5

我正在尝试编写一个解析CSV的脚本,并通过比较每一行并在必要时填补空白来填补空白。

e.g。在这个例子中,它会添加

"doc_3.doc", "date copied from date2"
"doc_4.doc", "date copied from date2"

我正在尝试用Ruby编写这个脚本,因为我正在尝试学习该语言,显然我误解了Ruby的循环方式,因为它不是典型的'for'循环,经常在PHP等中使用。

到目前为止,这是我的代码,对循环本身的任何帮助都将非常感谢!

#!/usr/bin/env ruby

require 'csv'

# Load file
csv_fname = './upload-list-docs.csv'

# Parsing function
def parse_csv(csv_fname)
    uploads = []
    last_number = 0

    # Regex to find number in doc_XXX.YYY
    regex_find_number = /(?<=\_)(.*?)(?=\.)/

    csv_content = CSV.read(csv_fname)

    # Skip header row
    csv_content.shift

    csv_content.each do |row|
        current_number = row[0].match regex_find_number
        current_date = row[1]
        last_date = current_date

        until last_number == current_number do
            uploads << [last_number, last_date]
            last_number += 1
        end
    end

    return uploads
end

puts parse_csv(csv_fname)

一些样本CSV

"file_name","date"
"doc_1.jpg","2011-05-11 09:16:05.000000000"
"doc_3.doc","2011-05-11 10:10:36.000000000"
"doc_4.doc","2011-05-11 10:17:19.000000000"
"doc_6.doc","2011-05-11 10:58:35.000000000"
"doc_7.pdf","2011-05-11 11:16:22.000000000"
"doc_8.pdf","2011-05-11 11:19:29.000000000"
"doc_9.docx","2011-05-11 11:40:03.000000000"
"doc_13.pdf","2011-05-11 12:26:32.000000000"
"doc_14.docx","2011-05-11 12:34:50.000000000"
"doc_15.doc","2011-05-11 12:40:12.000000000"
"doc_16.doc","2011-05-11 13:03:11.000000000"
"doc_17.doc","2011-05-11 13:03:58.000000000"
"doc_19.pdf","2011-05-11 13:25:07.000000000"
"doc_20.rtf","2011-05-11 13:34:26.000000000"
"doc_21.rtf","2011-05-11 13:35:25.000000000"
"doc_24.doc","2011-05-11 13:49:02.000000000"
"doc_25.doc","2011-05-11 14:05:04.000000000"
"doc_26.pdf","2011-05-11 14:18:26.000000000"
"doc_27.rtf","2011-05-11 14:30:19.000000000"
"doc_28.doc","2011-05-11 14:33:13.000000000"
"doc_29.jpg","2011-05-11 15:07:27.000000000"
"doc_30.doc","2011-05-11 15:22:30.000000000"
"doc_31.doc","2011-05-11 15:31:07.000000000"
"doc_34.doc","2011-05-11 15:51:56.000000000"
"doc_35.doc","2011-05-11 15:55:15.000000000"
"doc_36.doc","2011-05-11 16:06:46.000000000"
"doc_38.wps","2011-05-11 16:21:08.000000000"
"doc_39.doc","2011-05-11 16:30:57.000000000"
"doc_40.doc","2011-05-11 16:41:55.000000000"
"doc_43.JPG","2011-05-11 17:03:40.000000000"
"doc_46.doc","2011-05-11 17:28:13.000000000"
"doc_51.doc","2011-05-11 17:50:34.000000000"
"doc_52.doc","2011-05-11 18:03:13.000000000"
"doc_53.doc","2011-05-11 18:43:48.000000000"
"doc_54.doc","2011-05-11 18:54:45.000000000"
"doc_55.doc","2011-05-11 19:31:03.000000000"
"doc_56.doc","2011-05-11 19:31:23.000000000"
"doc_57.doc","2011-05-11 20:17:38.000000000"
"doc_59.jpg","2011-05-11 20:22:55.000000000"
"doc_61.pdf","2011-05-11 21:14:52.000000000"

3 个答案:

答案 0 :(得分:1)

以下是我编写代码的方法:

require 'csv'
csv_fname = './upload-list-docs.csv'

# Create a structure to get some easy methods:
Myfile = Struct.new(:name,:date){
  def number
    name[/(?<=\_)(.*?)(?=\.)/].to_i
  end
  def next_file
    Myfile.new(name.gsub(/(?<=\_)(.*?)(?=\.)/){|num|num.next}, date)
  end
}

# Read the content and add it to and array:
content = CSV.read(csv_fname)[1..-1].map{|data| Myfile.new(*data)}

# Add first entry to an result array:
result = [content.shift]

until content.empty?

 # Get new file:
 new_file = content.shift

 # Fill up with new files until we hit next file:
 files_between = new_file.number - result.last.number
 unless files_between == 1
   (files_between - 1).times do
     result << result.last.next_file
   end
 end

 # Add next file:
 result << new_file

end

# Map result back to array:
result.map!(&:to_a)

答案 1 :(得分:1)

OO方法。请注意,当我认为您希望空白填充[doc_X.doc, date]而不是[X, date]时,我这样做了 - 这种方法更合适,因为它需要@file_name上的更多正则表达式。现在这可能有点冗长,但它仍然有效并且非常易读。

require 'csv'

class Upload

  attr_reader :file_number, :date

  def initialize(file_name_or_number, date)
    @date = date
    @file_number = if file_name_or_number.is_a?(String)
                     file_name_or_number[/_(\d+)\./, 1].to_i
                   else
                     file_name_or_number
                   end
  end

  def to_a
    [@file_number, @date]
  end
end

class UploadCollection

  attr_reader :uploads

  def initialize(input_file)
    # Slice off all but the first element
    input_data = CSV.read(input_file)[1..-1] 
    # Create an array of Upload objects and sort by file number
    @uploads = input_data
                  .map { |row| Upload.new(row[0], row[1]) }
                  .sort_by(&:file_number)
  end

  def fill_blanks!
    # Get the smallest and largest file number
    # (they're sorted this way, remember)
    min, max = @uploads.first.file_number, @uploads.last.file_number
    # Create an array of all numbers between min and max, and
    # remove those elements already representing a file number
    missing = (min..max).to_a - @uploads.map(&:file_number)
    missing.each do |num|
      # Explaining how this works makes my head ache.  Check out the
      # docs for Array#insert.
      @uploads.insert(num - 1, Upload.new(num, @uploads[num-2].date))
    end

    # Non-ambiguous return value
    true
  end

  def to_a
    @uploads.map(&:to_a)
  end

  def write_csv(file_path)
    CSV.open(file_path, 'wb') do |csv|
      csv << ['file_number', 'date'] # Headers
      to_a.each { |u| csv << u }
    end
  end
end

file = 'fnames.csv'
collection = UploadCollection.new(file)
collection.fill_blanks!
puts collection.to_a
collection.write_csv('out.csv')

答案 2 :(得分:0)

问题不在于循环(除了危险的==,如上所述应该改为>=),而是从正则表达式匹配中提取整数。

current_number = row[0].match( regex_find_number )[0].to_i