【问题标题】:Ruby: Parsing a string representation of nested arrays into an Array?Ruby:将嵌套数组的字符串表示解析为数组?
【发布时间】:2010-12-18 07:29:45
【问题描述】:

假设我有字符串

"[1,2,[3,4,[5,6]],7]"

如何将其解析到数组中

[1,2,[3,4,[5,6]],7]

?

在我的用例中,嵌套结构和模式是完全任意的。

我目前的临时解决方案包括在每个句点后添加一个空格并使用YAML.load,但如果可能的话,我希望有一个更干净的。

(如果可能,不需要外部库)

【问题讨论】:

    标签: ruby regex arrays parsing


    【解决方案1】:

    正在使用JSON 正确解析该特定示例:

    s = "[1,2,[3,4,[5,6]],7]"
    #=> "[1,2,[3,4,[5,6]],7]"
    require 'json'
    #=> true
    JSON.parse s
    #=> [1, 2, [3, 4, [5, 6]], 7]
    

    如果这不起作用,您可以尝试通过eval 运行字符串,但您必须确保没有传递任何实际的ruby 代码,因为eval 可能被用作注入漏洞。

    编辑:这是一个简单的递归,基于正则表达式的解析器,没有验证,没有测试,不用于生产等:

    def my_scan s
      res = []
      s.scan(/((\d+)|(\[(.+)\]))/) do |match|
        if match[1]
          res << match[1].to_i
        elsif match[3]
          res << my_scan(match[3])
        end
      end
      res
    end
    
    s = "[1,2,[3,4,[5,6]],7]"
    p my_scan(s).first #=> [1, 2, [3, 4, [5, 6]], 7]
    

    【讨论】:

    • 我想使用它,但我无法让 json 在我的计算机上正常运行,而且无论如何它不会比 yaml 解决方案干净得多。有没有办法手动编码这个解析?
    • 不确定“不干净”是什么意思,因为它是解析它的一种方法调用。您当然可以自己编写一个简单的基于正则表达式的解析器,也可以使用专用工具,例如 treetop.rubyforge.org,但这些都不像 JSON.parse 恕我直言。
    • 哦,JSON 至少在 1.9.x 中是 Ruby 核心库的一部分。
    • 如果您有一个像s = "['hello', 2, 'test', 5.0]" 这样的多类型数组,JSON 将无法解析并出现一般错误unexpected token at ...。但是,YAML 确实如 @Arup 的 answer: YAML.load(s) =&gt; ["hello", 2, "test", 5.0] 所示工作。
    • @ChrisCirefice:那是因为单引号字符串不是有效的 JSON。
    【解决方案2】:

    同样可以使用 Ruby 标准 libaray YAML 来完成,如下所示:

    require 'yaml'
    s = "[1,2,[3,4,[5,6]],7]"
    YAML.load(s)
    # => [1, 2, [3, 4, [5, 6]], 7]
    

    【讨论】:

    • +1; YAML 成功加载多类型数组,例如"['hello', 2, 'test', 5.0]",其中 JSON 无法解析。
    • 这样的好处是,如果有 nil 元素,它不会抛出错误,但它会将 nil 输出为 'nil',所以仍然需要将其转换为 nil。
    【解决方案3】:

    “显然”最好的解决方案是编写自己的解析器。 [如果你喜欢编写解析器,以前从未做过并想学习新东西,或者想控制确切的语法]

    require 'parslet'
    
    class Parser < Parslet::Parser
      rule(:space)       { str(' ') }
      rule(:space?)      { space.repeat(0) }
      rule(:openbrace_)  { str('[').as(:op) >> space? }
      rule(:closebrace_) { str(']').as(:cl) >> space? }
      rule(:comma_)      { str(',') >> space?  }
      rule(:integer)     { match['0-9'].repeat(1).as(:int) }
      rule(:value)       { (array | integer) >> space? }
      rule(:list)        { value >> ( comma_ >> value ).repeat(0) }
      rule(:array)       { (openbrace_ >> list.maybe.as(:list) >> closebrace_ )}
      rule(:nest)        { space? >> array.maybe }
      root(:nest)
    end
    
    class Arr
      def initialize(args)
        @val = args
      end
      def val
        @val.map{|v| v.is_a?(Arr) ? v.val : v}
      end
    end
    
    
    class MyTransform < Parslet::Transform
      rule(:int => simple(:x))      { Integer(x) }
      rule(:op => '[', :cl => ']')  { Arr.new([]) }
      rule(:op => '[', :list => simple(:x), :cl => ']')   {  Arr.new([x]) }
      rule(:op => '[', :list => sequence(:x), :cl => ']')   { Arr.new(x) }
    end
    
    def parse(s)
      MyTransform.new.apply(Parser.new.parse(s)).val
    end
    
    parse " [   1  ,   2  ,  [  3  ,  4  ,  [  5   ,  6  , [ ]]   ]  ,  7  ]  "
    

    Parslet 转换会将单个值匹配为“简单”,但如果该值返回一个数组,您很快就会得到数组的数组,那么您必须开始使用子树。然而,返回对象很好,因为它们在转换上面的层时代表单个值......所以序列将匹配得很好。

    加上返回裸数组的麻烦,以及 Array([x]) 和 Array(x) 给你同样的东西的问题......你会得到非常混乱的结果。

    为了避免这种情况,我创建了一个名为 Arr 的辅助类,它表示一个项目数组。 然后我可以决定我传递给它的内容。然后我可以让解析器保留所有括号,即使您有@MateuszFryc 调用的示例:)(感谢@MateuszFryc)

    【讨论】:

    • 我会说它不一定是显然最好的解决方案,因为它取决于输入及其格式以及生成方式。但是,它是最灵活的一种。此外,一个完整的 parslet 示例是一种难得的享受,所以 +1 给你!
    • [[[1],[2,3]]] 怎么样?
    • @MateuszFryc - puts (parse "[[[1],[2,3]]]").inspect => [[1], [2, 3]] - 似乎有效我:)...哦,我明白了...我们丢失了外部括号?
    • @NigelThorne - 感谢您的更新,但现在您的解析器/转换器似乎仍然存在一些差异。看看例如“[]”数组,它产生[nil]。看起来规则 rule(:op =&gt; '[', :cl =&gt; ']') { Arr.new([]) } 永远不会匹配?正如你所想的那样。这与rule(:op =&gt; '[', :list =&gt; simple(:x), :cl =&gt; ']') { Arr.new([x]) } 相当匹配,因此出现了问题。
    • 您可以简单地在上述规则中添加紧凑:rule(:op =&gt; '[', :list =&gt; simple(:x), :cl =&gt; ']') { Arr.new([x].compact) } 和您认为已使用的 rule(:op =&gt; '[', :cl =&gt; ']') { Arr.new([]) },完全删除。
    【解决方案4】:

    使用评估

    array = eval("[1,2,[3,4,[5,6]],7]")
    

    【讨论】:

    • 这不是我的应用程序的一部分,我觉得可以放心让其容易受到注射,抱歉。
    • @Justin L.,“洁净室”+“沙盒”将保护您免受 eval 的危害:stackoverflow.com/questions/2045324/…。剩下要保护的就是运行很长时间的代码。超时可以解决这个问题。
    • 请在您的答案中添加安全风险注释,以免被否决。关于如何降低风险的一些建议也很有价值。
    • 我同意添加有关安全性警告的必要性,因此我否决了其他很好的解决方案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-04-07
    • 2016-06-13
    • 1970-01-01
    • 2017-02-02
    • 2014-07-24
    • 1970-01-01
    相关资源
    最近更新 更多