代码之家  ›  专栏  ›  技术社区  ›  Andy

Ruby解析器:#切片!内部#每个#索引为#缺少元素

  •  1
  • Andy  · 技术社区  · 14 年前

    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 回复  |  直到 14 年前
        1
  •  1
  •   Jörg W Mittag    14 年前

    突变 收藏 您正在对其进行迭代。这不可能 工作(在我看来,不应该。在这种情况下,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"]
    

    作为比较,这里有语法:

    • S码 *
    • 要素 颜色 | 单词
    • rgb格式 | 十六进制
    • rgb格式 rgb格式
    • RGB值 代币 代币
    • 十六进制 十六进制
    • 六角值 代币
    • 单词 代币
        2
  •  1
  •   John La Rooy    14 年前

    data 到位。

    当你击中 rgb 循环中的下一个元素是 255 between 在那个地方 是的,所以下一个元素是 hex

    when 'rgb' then rgb  = data.slice! i+1,3
    when 'hex' then hex  = data.slice! i+1,1
    
        3
  •  0
  •   Andy    14 年前

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

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