【问题标题】:Parser in Ruby: #slice! inside #each_with_index = missing elementRuby 中的解析器:#slice!在 #each_with_index = 缺少元素内
【发布时间】:2011-03-21 14:23:30
【问题描述】:

假设我想从数组中分离出某些元素组合。例如

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 中获取那些遗漏的元素?或者假设有更多的集合要提取,是否有更好的解决方案来解决这个问题?

【问题讨论】:

    标签: ruby parsing each slice


    【解决方案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 | hex
    • rgbrgb rgbvalues
    • rgbvaluestoken token token
    • 十六进制十六进制 十六进制值
    • 十六进制值令牌
    • 单词标记

    【讨论】:

    • 感谢您提供的好例子!我需要解析一个不太复杂的语法,它主要是一串以空格分隔的单词/数字(混合集),尽管使用 cmets 和带引号的字符串。我是这个问题的新手,所以你能告诉我在 Ruby 中制作这样的解析器的好库/教程/示例吗?
    • 甜心!.. 非常感谢您的宝贵时间,Jörg!非常感谢您的帮助。解析器看起来非常漂亮和清晰,但我需要一些时间来考虑如何添加其他类型的输入。我可能会再次出现关于解析器的另一个问题。
    • @Andrei:如果您是付费会员或 ACM,或者您是付费会员的公司/学校/学院/大学的学生或员工,您绝对应该查看这篇论文 Matthew S. Davis (Portal.ACM.Org/citation.cfm?id=345105.345113) 构建递归下降解析器的面向对象方法。它使用 Smalltalk 作为示例,但它们也大多直接适用于 Ruby。
    • @Andrei:另外,我发现将语法至少非正式地写下来并与代码并排放置很有帮助。
    • örg:感谢您的精彩参考和建议!我得到了这篇文章并试图阅读它。对我来说相当新的语言和相当多的对更好文章的引用。我仍然很难为我的语法编写格雷巴赫表格。有时间的话可以去看看吗? stackoverflow.com/questions/3363227/…
    【解决方案2】:

    因为您正在原地操纵data

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

    这样的事情可能更适合你:

    when 'rgb' then rgb  = data.slice! i+1,3
    when 'hex' then hex  = data.slice! i+1,1
    

    【讨论】:

    • 我明白了。那你会怎么做这样的提取呢? (这里,rgbhex 只是为了说明结果。我在这两种情况下的代码中都使用了color
    • 啊哈,.. 不,我需要从 data 中删除 'rgb'、'hex' 等(不区分大小写)
    【解决方案3】:

    这里有一个更好的解决方案

    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"]
    

    【讨论】:

      猜你喜欢
      • 2017-05-28
      • 1970-01-01
      • 2023-03-10
      • 2018-04-08
      • 2021-01-05
      • 1970-01-01
      • 2019-03-23
      • 1970-01-01
      • 2022-11-29
      相关资源
      最近更新 更多