Let’s say, I want to separate certain combinations of elements from an array. For example
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"]]
The code have done the correct extraction, but it missed the elements just after the extracted sets. So if my data array is
data = %w{ start before rgb 255 255 255 hex FFFFFF after end }
then
pp [rgb, hex, data]
# >> [["rgb", "255", "255", "255"],
# >> [],
# >> ["start", "before", "hex", "FFFFFF", "after", "end"]]
Why does it happen? How to get those missed elements inside #each_with_index? Or may be there is a better solution for this problem assuming that there are much more sets to extract?
The problem is that you are mutating the collection while you are iterating over it. This cannot possibly work. (And in my opinion, it shouldn’t. Ruby should raise an exception in this case, instead of silently allowing incorrect behavior. That’s what pretty much all other imperative languages do.)
This here is the best I could come up with while still keeping your original style:
However, what you have is a parsing problem and that should really be solved by a parser. A simple hand-rolled parser/state machine will probably be a little bit more code than the above, but it will be so much more readable.
Here’s a simple recursive-descent parser that solves your problem:
I really like recursive-descent parsers because their structure almost perfectly matches the grammar: just keep parsing elements until the input is empty. What is an element? Well, it’s a color specification or a stop word. What is a color specification? Well, it’s either an RGB color specification or a hex color specification. What is an RGB color specification? Well, it’s something that matches the Regexp
/rgb/ifollowed by RGB values. What are RGB values? Well, it’s just three numbers …Use it like so:
For comparison, here’s the grammar:
*|word|hex