2

I'm trying to write a Ruby method that will return true only if the input is a valid phone number, which means, among other rules, it can have spaces and/or dashes between the digits, but not before or after the digits.

In a sense, I need a method that does the opposite of String#strip! (remove all spaces except leading and trailing spaces), plus the same for dashes.

I've tried using String#gsub!, but when I try to match a space or a dash between digits, then it replaces the digits as well as the space/dash.

Here's an example of the code I'm using to remove spaces. I figure once I know how to do that, it will be the same story with the dashes.

def valid_phone_number?(number)
  phone_number_pattern = /^0[^0]\d{8}$/

  # remove spaces
  number.gsub!(/\d\s+\d/, "")
  
  return number.match?(phone_number_pattern)
end

What happens is if I call the method with the following input: valid_phone_number?(" 09 777 55 888 ") I get false because line 5 transforms the number into " 0788 ", i.e. it gets rid of the digits around the spaces as well as the spaces. What I want it to do is just to get rid of the inner spaces, so as to produce " 0977755888 ".

I've tried number.gsub!(/\d(\s+)\d/, "") and number.gsub!(/\d(\s+)\d/) { |match| "" } to no avail.

Thank you!!

4
  • 1
    Maybe all you want is to remove all non-digits? .gsub!(/\D+/, "")? Commented Jul 18, 2022 at 9:52
  • Depending on what you need to do with your validated phone numbers, you may want to consider storing them in E.164 format. This takes the form [+][country code][area code][local phone number] i.e. no spaces. API's like Twilio's SMS/voice verification service and many others use this format. twilio.com/docs/libraries/ruby Commented Jul 18, 2022 at 13:23
  • You need to be more precise about what constitutes a valid phone number. By your first paragraph 'cat1 ---- 2dog' and '1 ---- 2' should return true. In your following paragraphs (and in the title) you say you need a method the removes certain characters. The implication is that characters may need to be removed to determine if a string constitutes a valid phone number, which is not necessarily true. I suggest you simply state what constitutes a valid phone number, present the code you've tried, explain why it doesn't work and request solutions. Commented Jul 18, 2022 at 18:36
  • As I mentioned in my question, a valid phone number can have spaces between the digits, among other rules. I didn't think it was relevant to mention the other rules since that would bog down the question and distract the readers from the actual issue. Commented Jul 26, 2022 at 16:30

4 Answers 4

2

If you want to return a boolean, you might for example use a pattern that accepts leading and trailing spaces, and matches 10 digits (as in your example data) where there can be optional spaces or hyphens in between.

^ *\d(?:[ -]?\d){9} *$

For example

def valid_phone_number?(number)
  phone_number_pattern = /^ *\d(?:[ -]*\d){9} *$/
  return number.match?(phone_number_pattern)
end

See a Ruby demo and a regex demo.

Sign up to request clarification or add additional context in comments.

Comments

1

To remove spaces & hyphen inbetween digits, try:

(?:\d+|\G(?!^)\d+)\K[- ]+(?=\d)

See an online regex demo


  • (?: - Open non-capture group;
    • d+ - Match 1+ digits;
    • | - Or;
    • \G(?!^)\d+ - Assert position at end of previous match but (negate start-line) with following 1+ digits;
    • )\K - Close non-capture group and reset matching point;
  • [- ]+ - Match 1+ space/hyphen;
  • (?=\d) - Assert position is followed by digits.

p " 09 777 55 888  ".gsub(/(?:\d+|\G(?!^)\d+)\K[- ]+(?=\d)/, '')

Prints: " 0977755888 "

Comments

1

Using a very simple regex (/\d/ tests for a digit):

str = " 09 777 55 888  "
r = str.index(/\d/)..str.rindex(/\d/)
str[r] = str[r].delete(" -")
p str # => " 0977755888  "

Comments

1

Passing a block to gsub is an option, capture groups available as globals:

>> str = " 09 777 55 888   "

# simple, easy to understand
>> str.gsub(/(^\s+)([\d\s-]+?)(\s+$)/){ "#$1#{$2.delete('- ')}#$3" }
=> " 0977755888   "

# a different take on @steenslag's answer, to avoid using range.
>> s = str.dup; s[/^\s+([\d\s-]+?)\s+$/, 1] = s.delete("- "); s 
=> " 0977755888   "

Benchmark, not that it matters that much:

n = 1_000_000
puts(Benchmark.bmbm do |x|
     # just a match
     x.report("match") { n.times {str.match(/^ *\d(?:[ -]*\d){9} *$/) } }
     # use regex in []=
     x.report("[//]=") { n.times {s = str.dup; s[/^\s+([\d\s-]+?)\s+$/, 1] = s.delete("- "); s } }
     # use range in []=
     x.report("[..]=") { n.times {s = str.dup; r = s.index(/\d/)..s.rindex(/\d/); s[r] = s[r].delete(" -"); s } }
     # block in gsub
     x.report("block") { n.times {str.gsub(/(^\s+)([\d\s-]+?)(\s+$)/){ "#$1#{$2.delete('- ')}#$3" }} }
     # long regex
     x.report("regex") { n.times {str.gsub(/(?:\d+|\G(?!^)\d+)\K[- ]+(?=\d)/, "")} }
end)

Rehearsal -----------------------------------------
match   0.997458   0.000004   0.997462 (  0.998003)
[//]=   1.822698   0.003983   1.826681 (  1.827574)
[..]=   3.095630   0.007955   3.103585 (  3.105489)
block   3.515401   0.003982   3.519383 (  3.521392)
regex   4.761748   0.007967   4.769715 (  4.772972)
------------------------------- total: 14.216826sec

            user     system      total        real
match   1.031670   0.000000   1.031670 (  1.032347)
[//]=   1.859028   0.000000   1.859028 (  1.860013)
[..]=   3.074159   0.003978   3.078137 (  3.079825)
block   3.751532   0.011982   3.763514 (  3.765673)
regex   4.634857   0.003972   4.638829 (  4.641259)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.