Подтвердить что ты не робот

Преобразование названных совпадений в 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, и я действительно не хочу, чтобы обезьяна исправляла его.

Как я могу исправить эту проблему?

Мне было не так ясно, как я думал: нужно сохранить имена и хэш-подобную структуру.

4b9b3361

Ответ 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]) }