Парсер в Ruby: # слайс!внутри #each_with_index = отсутствует элемент - PullRequest
1 голос
/ 27 июля 2010

Допустим, я хочу отделить определенные комбинации элементов от массива.Например,

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?Или, может быть, есть лучшее решение для этой проблемы, если предположить, что существует гораздо больше наборов для извлечения?

Ответы [ 3 ]

1 голос
/ 27 июля 2010

Проблема в том, что вы мутируете коллекцию , в то время как вы итерируете ее. Это не может возможно работать. (И, на мой взгляд, этого не должно быть. В этом случае 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? Ну, это всего лишь три числа & hellip;

  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 & rarr; элемент *
  • элемент & rarr; цвет | слово
  • color & rarr; rgb | hex
  • rgb & rarr; rgb rgbvalues ​​
  • rgbvalues ​​ & rarr; токен токен токен
  • hex & rarr; hex hexvalue
  • шестнадцатеричное значение & rarr; лексема
  • word & rarr; лексема
1 голос
/ 27 июля 2010

Потому что вы манипулируете data на месте.

Когда вы нажимаете rgb, следующий элемент в цикле будет 255, но вы удаляете эти элементы, так что теперь between находится в том месте, где было rgb, поэтому следующий элемент - hex

Что-то вроде этого может работать лучше для вас:

when 'rgb' then rgb  = data.slice! i+1,3
when 'hex' then hex  = data.slice! i+1,1
0 голосов
/ 02 августа 2010

Вот немного более приятное решение

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"]
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...