将 MatchData 中的命名匹配转换为哈希

问题描述 投票:0回答:6

我有一个相当简单的正则表达式,但我想使用命名正则表达式来使其更清晰,然后迭代结果。

测试字符串:

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
方法,我真的不想对其进行猴子修补。

如何解决这个问题?

我没有我想的那么清楚:重点是保留名称和类似散列的结构。

ruby regex hash
6个回答
38
投票

如果您需要完整的哈希值:

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

10
投票

今天发布了新的 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
 上一样。


1
投票
我会以稍微不同的方式解决创建哈希的整个问题:

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

 将返回该值,以及包含键的数组的大小等。


1
投票

@Phrogz 的答案是正确的,但您可以为多个捕获指定相同的名称。 这是 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
    

0
投票
从 Ruby

3.3.0 symbolize_names: true

 选项可用于 
MatchData#named_captures

如果关键字参数

symbolize_names

 被赋予真值,则结果哈希中的键是 Symbols

m = "12".match(/(?<a>.)(?<b>.)(?<c>.)?/) #=> #<MatchData "12" a:"1" b:"2" c:nil> m.named_captures #=> {"a" => "1", "b" => "2", "c" => nil} m.named_captures(symbolize_names: true) #=> {:a => "1", :b => "2", :c => nil}
    

-1
投票
如果你想保留名字,你可以这样做

new_dimensions = {} dimensions.names.each { |k| new_dimensions[k] = my_operation(dimensions[k]) }
    
© www.soinside.com 2019 - 2024. All rights reserved.