问题是您正在改变集合而您正在迭代它。这可能不起作用。 (在我看来,它不应该。在这种情况下,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
| hex
-
rgb → rgb rgbvalues
-
rgbvalues → token token token
-
十六进制 → 十六进制 十六进制值
-
十六进制值 → 令牌
-
单词 → 标记