1

比方说,我想从数组中分离出某些元素组合。例如

data = %w{ start before rgb 255 255 255 between hex FFFFFF after end }
rgb, hex = [], []
data.each_with_index do |v,i|
  p [i,v]
  case v.downcase
    when 'rgb' then rgb  = data.slice! i,4
    when 'hex' then hex  = data.slice! i,2
  end
end
pp [rgb, hex, data]
# >> [0, "start"]
# >> [1, "before"]
# >> [2, "rgb"]
# >> [3, "hex"]
# >> [4, "end"]
# >> [["rgb", "255", "255", "255"],
# >>  ["hex", "FFFFFF"],
# >>  ["start", "before", "between", "after", "end"]]

该代码已完成正确的提取,但它错过了提取集之后的元素。所以如果我的数据数组是

data = %w{ start before rgb 255 255 255 hex FFFFFF after end }

然后

pp [rgb, hex, data]
# >> [["rgb", "255", "255", "255"],
# >>  [],
# >>  ["start", "before", "hex", "FFFFFF", "after", "end"]]

为什么会这样?如何把那些遗漏的元素放进去#each_with_index?或者假设有更多的集合要提取,对于这个问题是否有更好的解决方案?

4

3 回答 3

1

问题是您在迭代集合时正在改变集合。不可能奏效。(在我看来,它不应该。在这种情况下,Ruby 应该引发异常,而不是默默地允许不正确的行为。这几乎是所有其他命令式语言所做的。)

这是我能想到的最好的,同时仍然保持你原来的风格:

require 'pp'

data = %w[start before rgb 255 255 255 hex FFFFFF after end]

rgb_count = hex_count = 0

rgb, hex, rest = data.reduce([[], [], []]) do |acc, el|
  acc.tap do |rgb, hex, rest|
    next (rgb_count = 3  ; rgb << el) if /rgb/i =~ el
    next (rgb_count -= 1 ; rgb << el) if rgb_count > 0
    next (hex_count = 1  ; hex << el) if /hex/i =~ el
    next (hex_count -= 1 ; hex << el) if hex_count > 0
    rest << el
  end
end

data.replace(rest)

pp rgb, hex, data
# ["rgb", "255", "255", "255"]
# ["hex", "FFFFFF"]
# ["start", "before", "after", "end"]

但是,您所拥有的是解析问题,应该由解析器真正解决。一个简单的手动解析器/状态机可能会比上面的代码多一点,但它的可读性会好得多

这是一个简单的递归下降解析器,可以解决您的问题:

class ColorParser
  def initialize(input)
    @input = input.dup
    @rgb, @hex, @data = [], [], []
  end

  def parse
    parse_element until @input.empty?
    return @rgb, @hex, @data
  end

  private

  def parse_element
    parse_color or parse_stop_word
  end

  def parse_color
    parse_rgb or parse_hex
  end

  def parse_rgb
    return unless /rgb/i =~ peek
    @rgb << consume
    parse_rgb_values
  end

我真的很喜欢递归下降解析器,因为它们的结构几乎完全符合语法:只需继续解析元素,直到输入为空。什么是元素?嗯,这是一个颜色规范或停用词。什么是颜色规格?嗯,它要么是 RGB 颜色规范,要么是十六进制颜色规范。什么是 RGB 颜色规范?嗯,它与正则表达式/rgb/i后跟 RGB 值相匹配。什么是 RGB 值?好吧,这只是三个数字……

  def parse_rgb_values
    3.times do @rgb << consume.to_i end
  end

  def parse_hex
    return unless /hex/i =~ peek
    @hex << consume
    parse_hex_value
  end

  def parse_hex_value
    @hex << consume.to_i(16)
  end

  def parse_stop_word
    @data << consume unless /rgb|hex/i =~ peek
  end

  def consume
    @input.slice!(0)
  end

  def peek
    @input.first
  end
end

像这样使用它:

data = %w[start before rgb 255 255 255 hex FFFFFF after end]
rgb, hex, rest = ColorParser.new(data).parse

require 'pp'

pp rgb, hex, rest
# ["rgb", 255, 255, 255]
# ["hex", 16777215]
# ["start", "before", "after", "end"]

为了比较,这里是语法:

  • S元素*
  • 元素颜色 |
  • 颜色RGB | 十六进制
  • rgbrgb rgb 值
  • rgbvaluestoken token token
  • 十六进制→十六进制hex
  • 十六进制值令牌
  • 记号
于 2010-07-27T14:08:23.937 回答
1

因为你data在原地操纵。

当您点击rgb循环中的下一个元素时,将是255,但您正在删除这些元素,所以现在between位于原来的位置rgb,所以下一个元素是hex

像这样的东西可能对你更有效:

when 'rgb' then rgb  = data.slice! i+1,3
when 'hex' then hex  = data.slice! i+1,1
于 2010-07-27T13:03:52.627 回答
0

这是一个更好的解决方案

data = %w{ start before rgb 255 255 255 hex FFFFFF hex EEEEEE after end }
rest, rgb, hex = [], [], []
until data.empty?
  case (key = data.shift).downcase
    when 'rgb' then rgb  += [key] + data.shift(3)
    when 'hex' then hex  += [key] + data.shift(1)
    else rest << key
  end
end
p rgb, hex, rest
# >> ["rgb", "255", "255", "255"]
# >> ["hex", "FFFFFF", "hex", "EEEEEE"]
# >> ["start", "before", "after", "end"]
于 2010-08-02T14:04:19.977 回答