如何在Ruby中从文本文件创建CSV

时间:2016-10-31 10:08:26

标签: ruby csv

我需要从文本文件创建一个CSV文件,其中包含有关我的通话的结算数据。我的文本文件有如下结构:

01.02.2016 10:35:49 8998775 New York 3:35 0,00 0,00

我使用以下方式创建CSV:

require 'csv'
  @calls = File.new("modified_billing", "r")
  CSV.open("new.csv", 'wb', write_headers: true,
    headers: ["Date", "Time", "Phone number","City","Duration", "Cost", "Cost of call"]) do |csv|
    @calls.each do |call|
      csv << call.split(" ")
    end
  end

它适用于具有单一名称的城市,但显然它不适用于“纽约”,“拉斯维加斯”等,因为它创建了两列。

3 个答案:

答案 0 :(得分:6)

我想你差不多了。这是一种没有正则表达式的简单方法:

string = '01.02.2016 10:35:49 8998775 New York 3:35 0,00 0,00'
data = string.split(' ')

data.shift(3)
# => ["01.02.2016", "10:35:49", "8998775"]

data.pop(3)
# => ["3:35", "0,00", "0,00"]

data.join(' ')
# => "New York"

# putting it together
first, third, second = data.shift(3), data.pop(3), [data.join(' ')]
csv << first + second + third

更紧凑的东西,虽然有点难读:

data = call.split(' ')
csv << [data.shift(3), data.pop(3)].insert(1, data.join(' ')).flatten

答案 1 :(得分:4)

有很多方法可以解决这个问题。以下是我过去做过类似事情的方式:

str = '01.02.2016 10:35:49 8998775 New York 3:35 0,00 0,00'
/(\S+) (\S+) (\d+) (.+) (\S+) (\S+) (\S+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "New York", "3:35", "0,00", "0,00"]

str = '01.02.2016 10:35:49 8998775 Chicago 3:35 0,00 0,00'
/(\S+) (\S+) (\d+) (.+) (\S+) (\S+) (\S+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "Chicago", "3:35", "0,00", "0,00"]

由于您知道字段通常以空格分隔,因此您可以使用这些字段。

如果您需要更多地验证字段:

str = '01.02.2016 10:35:49 8998775 New York 3:35 0,00 0,00'
/([\d.]+) ([\d:]+) (\d+) (\D+?) ([\d+:]+) ([\d,]+) ([\d,]+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "New York", "3:35", "0,00", "0,00"]

str = '01.02.2016 10:35:49 8998775 Chicago 3:35 0,00 0,00'
/([\d.]+) ([\d:]+) (\d+) (\D+?) ([\d+:]+) ([\d,]+) ([\d,]+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "Chicago", "3:35", "0,00", "0,00"]

有时候我会使用更像模板的东西:

str = '01.02.2016 10:35:49 8998775 New York 3:35 0,00 0,00'
/((?:\d{2}\.){2}\d{4}) ((?:\d{2}:){2}\d{2}) (\d+) (\D+?) (\d+:\d+) ([\d,]+) ([\d,]+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "New York", "3:35", "0,00", "0,00"]

str = '01.02.2016 10:35:49 8998775 Chicago 3:35 0,00 0,00'
/((?:\d{2}\.){2}\d{4}) ((?:\d{2}:){2}\d{2}) (\d+) (\D+?) (\d+:\d+) ([\d,]+) ([\d,]+)/.match(str).captures
# => ["01.02.2016", "10:35:49", "8998775", "Chicago", "3:35", "0,00", "0,00"]

其中:

  • \d{2}表示“两位数”。
  • (?:\d{2}\.)表示“将两位数和一个.视为一个群组但不捕获(”记住“)它。
  • (?:\d{2}\.){2}表示“做两次”。
  • ((?:\d{2}\.){2}\d{4})表示“记住所有加上接下来的四位数字”。

知道你可以解决剩下的模式。

优点是一旦弄清楚模式,就需要弄清楚它们重复的次数。如果源文本稍后更改,则调整数字非常简单。这是正则表达式的强大功能,当你有重复的模式时它们就很棒。

所有这一切,我更喜欢不使用正则表达式,因为它们往往是脆弱的,如果你不知道引擎如何解析,他们可以真的减慢你的代码。相反,我会选择类似Damien's的内容,使用split拆分字符串,然后shiftpop导致城市被遗弃。

答案 2 :(得分:0)

这是一个与你的例子相符的正则表达式。没有其他线路,很难判断它是否适用于每次通话。对于无法与Regexp匹配的调用,您将收到“无法解析”警告。 如果有多个空格或制表符,则可以将所有''替换为'\ s +'。

if call=~/(\d\d\.\d\d\.\d\d\d\d) (\d\d:\d\d:\d\d) (\d+) (.*?) (\d+:\d\d) (\d+,\d\d) (\d+,\d\d)/ then
  csv << Regexp.last_match.captures
else
  puts "Cannot parse : #{call}"
end