Remove only non-leading and non-trailing spaces from a string in Ruby?

Viewed 95

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 Answers

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.

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 "

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  "

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)
Related