让我们说,我想从数组中分离元素的某些组合。例如
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
内找到遗漏的元素?或者可能有一个更好的解决方案,假设有更多的集合要提取?
答案 0 :(得分: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
答案 1 :(得分: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颜色规格?好吧,它与Regexp /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"]
为了比较,这是语法:
*
|
字 |
hex 答案 2 :(得分: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"]