Преобразование названных совпадений в MatchData в Hash
У меня довольно простое регулярное выражение, но я хотел использовать именованные регулярные выражения, чтобы сделать его более чистым, а затем перебрать результаты.
Строка тестирования:
testing_string = "111x222b333"
Мое регулярное выражение:
regexp = %r{
(?<width> [0-9]{3} ) {0}
(?<height> [0-9]{3} ) {0}
(?<depth> [0-9]+ ) {0}
\g<width>x\g<height>b\g<depth>
}x
dimensions = regexp.match(testing_string)
Эта работа похожа на очарование, но здесь, где проблема:
dimensions.each { |k, v| dimensions[k] = my_operation(v) }
# ERROR !
undefined method `each' for #<MatchData "111x222b333" width:"111" height:"222" depth:"333">.
В объекте MatchData нет метода each
, и я действительно не хочу, чтобы обезьяна исправляла его.
Как я могу исправить эту проблему?
Мне было не так ясно, как я думал: нужно сохранить имена и хэш-подобную структуру.
Ответы
Ответ 1
Если вам нужен полный хэш:
captures = Hash[ dimensions.names.zip( dimensions.captures ) ]
p captures
#=> {"width"=>"111", "height"=>"222", "depth"=>"333"}
Если вы просто хотите перебирать пары имя/значение:
dimensions.names.each do |name|
value = dimensions[name]
puts "%6s -> %s" % [ name, value ]
end
#=> width -> 111
#=> height -> 222
#=> depth -> 333
Альтернатива:
dimensions.names.zip( dimensions.captures ).each do |name,value|
# ...
end
[ dimensions.names, dimensions.captures ].transpose.each do |name,value|
# ...
end
dimensions.names.each.with_index do |name,i|
value = dimensions.captures[i]
# ...
end
Ответ 2
Итак, сегодня была выпущена новая версия Ruby (2.4.0) , которая включает множество новых функций, среди них функция # 11999, ака MatchData#named_captures
. Это означает, что теперь вы можете сделать это:
h = '12'.match(/(?<a>.)(?<b>.)(?<c>.)?/).named_captures
#=> {"a"=>"1", "b"=>"2", "c"=>nil}
h.class
#=> Hash
Итак, при изменении кода
dimensions = regexp.match(testing_string)
к
dimensions = regexp.match(testing_string).named_captures
И вы можете использовать метод each
для результата результата регулярного выражения, как и для любого другого Hash
.
Ответ 3
Я бы атаковал всю проблему создания хэша несколько иначе:
irb(main):052:0> testing_string = "111x222b333"
"111x222b333"
irb(main):053:0> hash = Hash[%w[width height depth].zip(testing_string.scan(/\d+/))]
{
"width" => "111",
"height" => "222",
"depth" => "333"
}
В то время как регулярное выражение является мощным, их вызов сирены может быть слишком заманчивым, и мы становимся втянутыми в попытки использовать их, когда есть более простые или простые способы решения чего-то. Это просто о чем подумать.
Чтобы отслеживать количество проверенных элементов, за комментарий OP:
hash = Hash[%w[width height depth].zip(scan_result = testing_string.scan(/\d+/))]
=> {"width"=>"111", "height"=>"222", "depth"=>"333"}
scan_result.size
=> 3
Также hash.size
вернет это, как и размер массива, содержащего ключи, и т.д.
Ответ 4
@Phrogz answer является правильным, если все ваши записи имеют уникальные имена, но вам разрешено указывать несколько снимков с одинаковым именем. Вот пример из документации Regexp.
Этот код поддерживает записи с повторяющимися именами:
captures = Hash[
dimensions.regexp.named_captures.map do |name, indexes|
[
name,
indexes.map { |i| dimensions.captures[i - 1] }
]
end
]
# Iterate over the captures
captures.each do |name, values|
# name is a String
# values is an Array of Strings
end
Ответ 5
Если вы хотите сохранить имена, вы можете сделать
new_dimensions = {}
dimensions.names.each { |k| new_dimensions[k] = my_operation(dimensions[k]) }