Ruby: Анализ строкового представления вложенных массивов в массив?
Скажем, у меня была строка
"[1,2,[3,4,[5,6]],7]"
Как бы разобрать это в массиве
[1,2,[3,4,[5,6]],7]
?
Вложенные структуры и шаблоны полностью произвольны в моем случае использования.
Мое текущее решение ad-hoc включает добавление пробела после каждого периода и использование YAML.load
, но я хотел бы иметь более чистый, если это возможно.
(тот, который не требует внешних библиотек, если это возможно)
Ответы
Ответ 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
, но вы должны убедиться, что никакого реального рубинового кода, поскольку 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]
Ответ 2
То же самое можно сделать с помощью стандартного libaray Ruby YAML
, как показано ниже:
require 'yaml'
s = "[1,2,[3,4,[5,6]],7]"
YAML.load(s)
# => [1, 2, [3, 4, [5, 6]], 7]
Ответ 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)
Ответ 4
Использовать eval
array = eval("[1,2,[3,4,[5,6]],7]")