How to do named capture in ruby

RubyRegex

Ruby Problem Overview


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

[]

Ruby Solutions


Solution 1 - Ruby

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"}

Solution 2 - Ruby

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"}

Solution 3 - Ruby

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.

Solution 4 - Ruby

A way to turn capture group names and their values into a hash is to use a regex with named captures using (?<capture_name> and then access the %~ global "last match" variable.

regex_with_named_capture_groups = %r'(?<area>\d{3})-(?<city>\d{3})-(?<local>\d{4})'
"555-333-7777"[regex_with_named_capture_groups]

match_hash = $~.names.inject({}){|mem, capture| mem[capture] = $~[capture]; mem}
# => {"area"=>"555", "city"=>"333", "local"=>"7777"}

# If ActiveSupport is available
match_hash.symbolize_keys!
# => {area: "555", city: "333", local: "7777"}

Solution 5 - Ruby

This alternative also works:

regex = /^(?<area>\d+)\-(?<city>\d+)\-(?<local>\d+)$/
m = "555-333-7777".match regex
m.named_captures
 => {"area"=>"555", "city"=>"333", "local"=>"7777"}

Solution 6 - Ruby

There are a LOT of ways to create named captures, many of which have been mentioned already. For the record though, we could have even used the originally posted code along with Multiple Assignment like so:

a, b, c =  "555-333-7777".scan(/(\d{3})-(\d{3})-(\d{4})/).flatten
hash = {area: a, city: b, local: c}
#=>  {:area=>"555", :city=>"333", :local=>"7777"}

OR

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

OR along with zip and optionally to_h:

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

([:area, :city, :local].zip "555-333-7777".scan(/(\d{3})-(\d{3})-(\d{4})/).flatten).to_h
#=>  {:area=>"555", :city=>"333", :local=>"7777"}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRubyNoobieView Question on Stackoverflow
Solution 1 - RubySergio TulentsevView Answer on Stackoverflow
Solution 2 - RubyKimmo LehtoView Answer on Stackoverflow
Solution 3 - RubyChristopher OezbekView Answer on Stackoverflow
Solution 4 - RubyEpigeneView Answer on Stackoverflow
Solution 5 - RubyTiloView Answer on Stackoverflow
Solution 6 - RubyMichael BView Answer on Stackoverflow