Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to mask all but last four characters in a string

Tags:

ruby

I've been attempting a coding exercise to mask all but the last four digits or characters of any input.

I think my solution works but it seems a bit clumsy. Does anyone have ideas about how to refactor it?

Here's my code:

def mask(string)
  z = string.to_s.length

  if z <= 4
    return string
  elsif z > 4
    array = []
    string1 = string.to_s.chars

    string1[0..((z-1)-4)].each do |s|
      array << "#"
    end

    array << string1[(z-4)..(z-1)]

    puts array.join(", ").delete(", ").inspect     
  end
end
like image 513
user211309 Avatar asked Nov 29 '22 22:11

user211309


1 Answers

positive lookahead

A positive lookahead makes it pretty easy. If any character is followed by at least 4 characters, it gets replaced :

"654321".gsub(/.(?=.{4})/,'#')
# "##4321"

Here's a description of the regex :

r = /
     .        # Just one character
     (?=      # which must be followed by
        .{4}  # 4 characters
     )        #
    /x        # free-spacing mode, allows comments inside regex

Note that the regex only matches one character at a time, even though it needs to check up to 5 characters for each match :

"654321".scan(r)
# => ["6", "5"]

/(.)..../ wouldn't work, because it would consume 5 characters for each iteration :

"654321".scan(/(.)..../)
# => [["6"]]
"abcdefghij".scan(/(.)..../)
# => [["a"], ["f"]]

If you want to parametrize the length of the unmasked string, you can use variable interpolation :

all_but = 4
/.(?=.{#{all_but}})/
# => /.(?=.{4})/

Code

Packing it into a method, it becomes :

def mask(string, all_but = 4, char = '#')
  string.gsub(/.(?=.{#{all_but}})/, char)
end

p mask('testabcdef')
# '######cdef'
p mask('1234')
# '1234'
p mask('123')
# '123'
p mask('x')
# 'x'

You could also adapt it for sentences :

def mask(string, all_but = 4, char = '#')
  string.gsub(/\w(?=\w{#{all_but}})/, char)
end

p mask('It even works for multiple words')
# "It even #orks for ####iple #ords"

Some notes about your code

string.to_s

Naming things is very important in programming, especially in dynamic languages.

string.to_s

If string is indeed a string, there shouldn't be any reason to call to_s.

If string isn't a string, you should indeed call to_s before gsub but should also rename string to a better description :

object.to_s
array.to_s
whatever.to_s

join

puts array.join(", ").delete(", ").inspect

What do you want to do exactly? You could probably just use join :

[1,2,[3,4]].join(", ").delete(", ")
# "1234"
[1,2,[3,4]].join
# "1234"

delete

Note that .delete(", ") deletes every comma and every whitespace, in any order. It doesn't only delete ", " substrings :

",a b,,,   cc".delete(', ')
# "abcc"
["1,2", "3,4"].join(', ').delete(', ')
# "1234"
like image 82
Eric Duminil Avatar answered Dec 22 '22 09:12

Eric Duminil