Ruby: парсинг строкового представления вложенных массивов в массив? - PullRequest
17 голосов
/ 18 декабря 2010

Допустим, у меня была строка

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

Как бы я мог разобрать это в массив

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

?

Вложенные структуры и шаблоны совершенно произвольны вмой случай использования.

Мое текущее специальное решение включает добавление пробела после каждого периода и использование YAML.load, но я бы хотел иметь более чистый, если это возможно.

(Одинкоторый не требует внешних библиотек, если это возможно)

Ответы [ 4 ]

40 голосов
/ 18 декабря 2010

Этот конкретный пример правильно анализируется с использованием 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]
14 голосов
/ 24 июня 2013

То же самое можно сделать, используя стандартную библиотеку Ruby YAML, как показано ниже:

require 'yaml'
s = "[1,2,[3,4,[5,6]],7]"
YAML.load(s)
# => [1, 2, [3, 4, [5, 6]], 7]
4 голосов
/ 13 июля 2015

«Очевидно», лучшее решение - написать собственный парсер. [Если вам нравится писать парсеры, вы никогда не делали этого раньше и хотите узнать что-то новое или хотите контролировать точную грамматику]

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

Преобразования Парслета будут соответствовать одному значению как «простому», но если это значение возвращает массив, вы скоро получите массивы массивов, и вам придется начать использовать поддерево. возвращающие объекты, однако, хороши, поскольку они представляют одно значение при преобразовании слоя выше ... так что последовательность будет соответствовать отлично.

Соедините проблему с возвратом пустых массивов с проблемой того, что Array ([x]) и Array (x) дают вам одно и то же ... и вы получите очень запутанные результаты.

Чтобы избежать этого, я создал вспомогательный класс Arr, представляющий массив элементов. Я мог бы тогда продиктовать то, что я передаю в это. Тогда я смогу заставить синтаксический анализатор оставить все скобки, даже если у вас есть пример, который вызвала @MateuszFryc :) (спасибо @MateuszFryc)

0 голосов
/ 18 декабря 2010

Используйте eval

array = eval("[1,2,[3,4,[5,6]],7]")
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...