【问题标题】:How to create CSV from a text file in Ruby如何从 Ruby 中的文本文件创建 CSV
【发布时间】:2025-12-31 20:40:01
【问题描述】:

我需要从文本文件创建一个 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

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

【问题讨论】:

  • 您必须确定您将获得的文本文件是否每次都采用相同的格式。查看您的文本文件的模式,我想您可以使用正则表达式将数字部分截断至New York,然后将其截断以处理剩余数据
  • 感谢您的建议,但我应该如何同时将数字彼此分开?

标签: ruby csv


【解决方案1】:

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

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

【讨论】:

  • 非常好!您应该为 OP 添加最后一步,将其拉到一起...first = data.shift(3); third = data.pop(3); second = [data.join(' ')]; csv &lt;&lt; first + second + third
  • 会的!谢谢:)
  • 哈哈,更好:)
【解决方案2】:

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

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 导致城市被留下。

【讨论】:

  • 很好的解释!我喜欢我女朋友说“(?:\d{2}\.){2}”
【解决方案3】:

这是一个与您的示例匹配的正则表达式。如果没有其他线路,很难判断它是否适用于每次通话。对于未与 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

【讨论】:

  • 您的正则表达式可以大大简化。看看使用{n} 和/或重复非捕获组。
  • 当然,我可以使用 \d{2} 代替 \d\d​​,但我不觉得它更具可读性,而且它实际上更长。只有 \d{4} 才值得。另外,我希望每个组都被捕获以直接获取一个数组。你的例子会是什么样子?如果你想挑剔,“和/或”只是“或”ewriteonline.com/using-andor-in-a-sentence-is-just-plain-dumb ;)