2

给定一个逗号分隔的字符串和另一个指示类型的数组,如何构造一个不同类型的数组?


通过解析取自 的 CSV 输入stdin,我有一个列标题数组Symbol

cols = [:IndexSymbol, :PriceStatus, :UpdateExchange, :Last]

和一行原始输入:

raw = "$JX.T.CA,Open,T,933.36T 11:10:00.000"

我想cellsraw输入构造一个数组,其中的每个元素cells都是由cols. 这样做的惯用 Ruby-sh 方法是什么?


我试过这个,它有效,但感觉不太对。

1)首先,为每个需要封装的类型定义一个类:

class Sku
  attr_accessor :mRoot, :mExch,, :mCountry
  def initialize(root, exch, country)
    @mRoot = root
    @mExch = exch
    @mCountry = country
  end
end

class Price
  attr_accessor :mPrice, :mExchange, :mTime
  def initialize(price, exchange, time)
    @mPrice = price
    @mExchange = exchange
    @mTime = time
  end
end

2)然后,为每个需要转换的唯一列类型定义转换函数:

def to_sku(raw)
  raw.match('(\w+)\.(\w{0,1})\.(\w{,2})') { |m| Sku.new(m[1], m[2], m[3])}
end

def to_price(raw)

end

3) 从输入创建一个字符串数组:

cells = raw.split(",")

cells4)最后通过构造相应列标题所指示的类型来修改每个元素:

cells.each_index do |i|
    cells[i] = case cols[i]
        when :IndexSymbol
            to_sku(cells[i])
        when :PriceStatus
            cells[i].split(";").collect {|st| st.to_sym}
        when :UpdateExchange
            cells[i]
        when :Last
            cells[i].match('(\d*\.*\d*)(\w?) (\d{1,2}:\d{2}:\d{2}\.\d{3})') { |m| Price.new(m[1], m[2], m[3])}
        else
            puts "Unhandled column type (#{cols[i]}) from input string: \n#{cols}\n#{raw}"
            exit -1
    end
end

感觉不对的部分是第 3 步和第 4 步。这是如何以更 Ruby 的方式完成的?我在想像这样一种超级简洁的方法,它只存在于我的想象中:

cells = raw.split_using_convertor(",")
4

4 回答 4

2

#zip您可以使用、#map解构赋值使第四步更简单:

cells = cells.zip(cols).map do |cell, col|
    case col
    when :IndexSymbol
        to_sku(cell)
    when :PriceStatus
        cell.split(";").collect {|st| st.to_sym}
    when :UpdateExchange
        cell
    when :Last
        cell.match('(\d*\.*\d*)(\w?) (\d{1,2}:\d{2}:\d{2}\.\d{3})') { |m| Price.new(m[1], m[2], m[3])}
    else
        puts "Unhandled column type (#{col}) from input string: \n#{cols}\n#{raw}"
        exit -1
    end
end

我不建议将该步骤与拆分相结合,因为解析一行 CSV 非常复杂,足以成为它自己的步骤。有关如何解析 CSV,请参阅我的评论。

于 2013-08-19T15:23:18.347 回答
2

您可以从基类继承不同的类型,并将查找知识放在该基类中。然后你可以让每个类知道如何从原始字符串初始化自己:

class Header
  @@lookup = {}

  def self.symbol(*syms)
    syms.each{|sym| @@lookup[sym] = self}
  end

  def self.lookup(sym)
    @@lookup[sym]
  end
end

class Sku < Header
  symbol :IndexSymbol
  attr_accessor :mRoot, :mExch, :mCountry

  def initialize(root, exch, country)
    @mRoot = root
    @mExch = exch
    @mCountry = country
  end

  def to_s
    "@#{mRoot}-#{mExch}-#{mCountry}"
  end

  def self.from_raw(str)
    str.match('(\w+)\.(\w{0,1})\.(\w{,2})') { |m| new(m[1], m[2], m[3])}
  end
end

class Price < Header
  symbol :Last, :Bid
  attr_accessor :mPrice, :mExchange, :mTime

  def initialize(price, exchange, time)
    @mPrice = price
    @mExchange = exchange
    @mTime = Time.new(time)
  end

  def to_s
    "$#{mPrice}-#{mExchange}-#{mTime}"
  end

  def self.from_raw(raw)
    raw.match('(\d*\.*\d*)(\w?) (\d{1,2}:\d{2}:\d{2}\.\d{3})') { |m| new(m[1], m[2], m[3])}
  end
end

class SymbolList
  symbol :PriceStatus
  attr_accessor :mSymbols

  def initialize(symbols)
    @mSymbols = symbols
  end

  def self.from_raw(str)
    new(str.split(";").map(&:to_sym))
  end

  def to_s
    mSymbols.to_s
  end
end

class ExchangeIdentifier
  symbol :UpdateExchange
  attr_accessor :mExch

  def initialize(exch)
    @mExch = exch
  end

  def self.from_raw(raw)
    new(raw)
  end

  def to_s
    mExch
  end
end

然后您可以像这样替换第 4 步(不包括 CSV 解析):

cells.each_index.map do |i|
  Header.lookup(cols[i]).from_raw(cells[i])
end
于 2013-08-19T15:27:06.270 回答
1

Ruby 的CSV 库直接包含对此类事物的支持(以及更好地处理实际解析),尽管文档有点尴尬。

您需要提供一个proc将为您进行转换的选项,并将其作为选项传递给CSV.parse

converter = proc do |field, info|
  case info.header.strip # in case you have spaces after your commas
  when "IndexSymbol"
      field.match('(\w+)\.(\w{0,1})\.(\w{,2})') { |m| Sku.new(m[1], m[2], m[3])}
  when "PriceStatus"
      field.split(";").collect {|st| st.to_sym}
  when "UpdateExchange"
      field
  when "Last"
      field.match('(\d*\.*\d*)(\w?) (\d{1,2}:\d{2}:\d{2}\.\d{3})') { |m| Price.new(m[1], m[2], m[3])}
  end
end

然后您几乎可以直接将其解析为您想要的格式:

c =  CSV.parse(s, :headers => true, :converters => converter).by_row!.map do |row|
  row.map { |_, field| f }  #we only want the field now, not the header
end
于 2013-08-19T15:40:20.300 回答
1

@AbeVoelker 的回答引导我朝着正确的方向前进,但是由于我在 OP 中没有提到的一些事情,我不得不做出相当大的改变。

一些单元格将属于同一类型,但仍具有不同的语义。这些语义差异不会在这里发挥作用(也没有详细说明),但它们在我正在编写的工具的更大范围内发挥作用。

例如,将有几个类型为Price; 其中一些是:Last,':Bid:Ask. 它们都是相同的类型 ( ),但它们仍然有足够的不同,因此所有列Price都不能有一个Header@@lookup条目。Price

所以我实际上做的是为每种类型的细胞编写一个自解码类(这个关键部分归功于 Abe):

class Sku
    attr_accessor :mRoot, :mExch, :mCountry
    def initialize(root, exch, country)
        @mRoot = root
        @mExch = exch
        @mCountry = country
    end

    def to_s
        "@#{mRoot}-#{mExch}-#{mCountry}"
    end

    def self.from_raw(str)
        str.match('(\w+)\.(\w{0,1})\.(\w{,2})') { |m| new(m[1], m[2], m[3])}
    end
end

class Price
    attr_accessor :mPrice, :mExchange, :mTime
    def initialize(price, exchange, time)
        @mPrice = price
        @mExchange = exchange
        @mTime = Time.new(time)
    end
    def to_s
        "$#{mPrice}-#{mExchange}-#{mTime}"
    end
    def self.from_raw(raw)
        raw.match('(\d*\.*\d*)(\w?) (\d{1,2}:\d{2}:\d{2}\.\d{3})') { |m| new(m[1], m[2], m[3])}
    end
end

class SymbolList
    attr_accessor :mSymbols
    def initialize(symbols)
        @mSymbols = symbols
    end
    def self.from_raw(str)
        new(str.split(";").collect {|s| s.to_sym})
    end
    def to_s
        mSymbols.to_s
    end
end

class ExchangeIdentifier
    attr_accessor :mExch
    def initialize(exch)
        @mExch = exch
    end
    def self.from_raw(raw)
        new(raw)
    end
    def to_s
        mExch
    end
end

...创建一个类型列表,将每个列标识符映射到类型:

ColumnTypes =
{
    :IndexSymbol => Sku,
    :PriceStatus => SymbolList,
    :UpdateExchange => ExchangeIdentifier,
    :Last => Price,
    :Bid => Price
}

...最后Array通过调用适当的类型来构造我的单元格from_raw

cells = raw.split(",").each_with_index.collect { |cell,i|
    puts "Cell: #{cell}, ColType: #{ColumnTypes[cols[i]]}"
    ColumnTypes[cols[i]].from_raw(cell)
}

结果是代码在我看来干净且富有表现力,并且看起来比我最初所做的更 Ruby 风格。

完整的例子在这里

于 2013-08-19T16:42:22.180 回答