Ruby:将嵌套数组的字符串表示解析为数组?

Jus*_* L. 17 ruby regex arrays parsing

假设我有字符串

"[1,2,[3,4,[5,6]],7]"
Run Code Online (Sandbox Code Playgroud)

我如何将其解析为数组

[1,2,[3,4,[5,6]],7]
Run Code Online (Sandbox Code Playgroud)

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

我目前的临时解决方案是在每个时段之后添加一个空格并使用YAML.load,但是如果可能的话我想要一个更干净的空间.

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

Mla*_*vić 40

正在使用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]
Run Code Online (Sandbox Code Playgroud)

如果这不起作用,您可以尝试运行字符串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]
Run Code Online (Sandbox Code Playgroud)


Aru*_*hit 14

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

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


Nig*_*rne 5

显然,最好的解决方案是编写自己的解析器。[如果您喜欢编写解析器,以前从未做过,并且想学习新知识,或者想控制确切的语法,就可以使用[

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  ]  "
Run Code Online (Sandbox Code Playgroud)

Parslet转换将单个值匹配为“ simple”,但如果该值返回一个数组,则很快就会得到数组数组,那么您必须开始使用子树。但是,返回的对象很好,因为在转换上方的图层时它们表示单个值...因此序列将匹配。

再加上返回裸数组的麻烦,以及Array([x])和Array(x)给您同样的问题……您会得到非常令人困惑的结果。

为了避免这种情况,我创建了一个称为Arr的帮助程序类,该类代表一系列项目。然后,我可以决定我要传递给它的内容。然后,即使您有@MateuszFryc呼出的示例,我也可以让解析器保留所有括号:(感谢@MateuszFryc)

  • 我会说这不一定是“显然最好的”解决方案,因为它取决于输入及其格式以及生成方式。但是,它是最灵活的之一。另外,一个完整的工作parslet示例是一种罕见的享受,因此对您+1! (2认同)