How to do named capture in ruby

I want to name the capture of string that I get from scan. How to do it?

"555-333-7777".scan(/(\d{3})-(\d{3})-(\d{4})/).flatten #=> ["555", "333", "7777"]

Is it possible to turn it into like this

{:area => "555", :city => "333", :local => "7777" }

or

[["555","area"], [...]]

I tried

"555-333-7777".scan(/((?<area>)\d{3})-(\d{3})-(\d{4})/).flatten

but it returns

[]

You should use match with named captures, not scan

m = "555-333-7777".match(/(?<area>\d{3})-(?<city>\d{3})-(?<number>\d{4})/)
m # => #<MatchData "555-333-7777" area:"555" city:"333" number:"7777">
m[:area] # => "555"
m[:city] # => "333"

If you want an actual hash, you can use something like this:

m.names.zip(m.captures).to_h # => {"area"=>"555", "city"=>"333", "number"=>"7777"}

Or this (ruby 2.4 or later)

m.named_captures # => {"area"=>"555", "city"=>"333", "number"=>"7777"}

Something like this?

"555-333-7777" =~ /^(?<area>\d+)\-(?<city>\d+)\-(?<local>\d+)$/
Hash[$~.names.collect{|x| [x.to_sym, $~[x]]}]
 => {:area=>"555", :city=>"333", :local=>"7777"}

Bonus version:

Hash[[:area, :city, :local].zip("555-333-7777".split("-"))]
=> {:area=>"555", :city=>"333", :local=>"7777"}

In case you don't really need the hash, but just local variables:

if /(?<area>\d{3})-(?<city>\d{3})-(?<number>\d{4})/ =~ "555-333-7777"
  puts area
  puts city
  puts number
end

How does it work?

  • You need to use =~ regex operator.
  • The regex (sadly) needs to be on the left. It doesn't work if you use string =~ regex.
  • Otherwise it is the same syntax ?<var> as with named_captures.
  • It is supported in Ruby 1.9.3!

Official documentation:

When named capture groups are used with a literal regexp on the left-hand side of an expression and the =~ operator, the captured text is also assigned to local variables with corresponding names.