loading...

Daily Challenge #179 - Hide Phone Numbers

thepracticaldev profile image dev.to staff ・1 min read

Setup

Implement a function that will hide the last six digits of each phone number. Your function should be able to understand the separators for US numbers. Phone numbers can be separated by spaces, periods, or hyphens.

Examples

Original: 201-680-0202
Encrypted: `201-6XX-XXXX

Tests

encryptNum("328 6587120")
encryptNum("212-420-0202")
encryptNum("211-458-7851")


This challenge comes from otrebor6 on CodeWars. Thank you to CodeWars, who has licensed redistribution of this challenge under the 2-Clause BSD License!

Want to propose a challenge idea for a future post? Email yo+challenge@dev.to with your suggestions!

Discussion

pic
Editor guide
Collapse
avaq profile image
Aldwin Vlasblom

JavaScript:

const encryptNum = num => num.replace(/(\d[ .-]?){6}$/, x => x.replace(/\d/g, 'X'))

The first replace finds the last 6 digits, with or without trailing separators.
The resulting match undergoes another replace call to substitute any digits for X's.

The first expression can also be relaxed to allow any separators: /(\d[^\d]?){6}$/, or even any number of separators: /(\d[^\d]*){6}$/.

Collapse
alvaromontoro profile image
Alvaro Montoro

This is neat

Collapse
cirlorm_io profile image
Collapse
epranka profile image
Edvinas Pranka

Javascript

function encryptNum(num){
  return num.substr(0, 5) + 'XX-XXXX'
}
Collapse
exts profile image
Lamonte

This only works if you're expecting a certain value. Output would be weird otherwise.

Collapse
epranka profile image
Edvinas Pranka

Yeah, but the data validation is not required in this challange.

Thread Thread
georgewl profile image
George W Langham

True, but it does expect the tests to all pass still, which they won't with that solution

Collapse
aminnairi profile image
Amin

Nice short solution!

If I'm being correct, for the string "328 6587120", the expected output should be "328 6XXXXXX".

But in your case, the output would be "328 6XX-XXXX".

Collapse
idanarye profile image
Idan Arye

Rust:

fn encrypt_num(phone_number: String) -> String {
    let mut bytes = phone_number.into_bytes();
    for ch in bytes.iter_mut().rev().filter(|b| {
        let c = **b as char;
        '0' <= c && c <= '9'
    }).take(6) {
        *ch = 'X' as u8;
    }
    String::from_utf8(bytes).unwrap()
}

fn main() {
    assert_eq!(encrypt_num("328 6587120".to_owned()), "328 6XXXXXX");
    assert_eq!(encrypt_num("212-420-0202".to_owned()), "212-4XX-XXXX");
    assert_eq!(encrypt_num("211-458-7851".to_owned()), "211-4XX-XXXX");
}
Collapse
aminnairi profile image
Amin

Elm

encryptDigit : Char -> Char
encryptDigit character =
    if Char.isDigit character then
        'X'

    else
        character


encryptNum : String -> String
encryptNum string =
    String.map encryptDigit ( String.dropLeft 5 string )
        |> String.append ( String.left 5 string )
Collapse
alvaromontoro profile image
Alvaro Montoro

JavaScript

If we want all the phone numbers to have the same format, we could use a regular expression to detect all the digits (assuming that the phone number is correct) and format it accordingly:

const encryptNum = (num) => {
  const nums = num.match(/\d/gi).join("");
  return `${nums.substring(0,3)}-${nums.substring(3,6)}-${nums.substring(6)}`;
}

If we want to keep the original format (and again, assuming the phone number is correct), another solution would be transforming the string into an array (strings are immutable, an array would be easier to operate), parse the last 6 digits and return its concatenation:

const encryptNum = (num) => {
  let numbers = 0;
  let position = num.length - 1;
  let formattedNum = num.split("");

  while (numbers < 6 && position > 0) {
    if (num[position] >= "0" && num[position] <= "9") { 
      formattedNum[position] = "x";
      numbers++;
    }
    position--;
  }

  return formattedNum.join("");
}
Collapse
cipharius profile image
Valts Liepiņš

Haskell, similar idea to my solution in Ruby

import Data.Char (isDigit)

encryptNum :: String -> String
encryptNum = reverse . encryptDigit 0 . reverse
    where
        encryptDigit 6 xs     = xs
        encryptDigit i (x:xs) | isDigit x = 'X' : encryptDigit (i+1) xs
                              | otherwise =  x  : encryptDigit i xs
Collapse
zoejm profile image
zoe-j-m

Scala,

def encryptNum(input : String) = {
  def encryptInner(remain : Int, toGo : List[Char]) : List[Char] = {
    toGo match {
      case Nil => Nil
      case _ if remain == 0 => toGo
      case x::xs if x.isDigit => 'X' :: encryptInner(remain - 1, xs)
      case x::xs => x :: encryptInner(remain, xs)
    }
  }

  encryptInner(6, input.reverse.toList).reverse.mkString
}
Collapse
candidateplanet profile image
lusen / they / them 🏳️‍🌈🥑

Python, a straightforward approach that would be fast to design and implement without bugs during an interview. It is big-O optimal and easy to understand and refactor.

def encrypt(phone_num):
  # define some const. easy to refactor into method sig or config elsewhere later
  ENCRYPT_NUMBER = 6
  ENCRYPT_SYMBOL = 'X'

  # we'll return this at the end - reversed and joined.
  encrypted = []
  # count the numbers we encrypt
  counter = 0

  # start at the end and encrypt numbers until we reach ENCRYPT_NUMBER
  for ch in phone_num[::-1]:
    # we're done encrypting so pass everything along
    if counter == ENCRYPT_NUMBER:
      encrypted.append(ch)
      continue

    try:
      # if character is a number, encrypt it
      int(ch)
      counter += 1
      encrypted.append(ENCRYPT_SYMBOL)
    except ValueError:
      # character is not a number so pass it  through
      encrypted.append(ch)

  # return a string in the right order
  return ''.join(encrypted[::-1])

print(encrypt("328 6587120")) # 328 6XXXXXX
print(encrypt("212-420-0202")) # 212-4XX-XXXX
print(encrypt("211-458-7851")) # 211-4XX-XXXX
print(encrypt("211 458 7851")) # 211 4XX XXXX
print(encrypt("2114587851")) # 2114XXXXXX
Collapse
mellen profile image
Matt Ellen

ONE REGEX TO RULE THEM

Put your separators where you want! I'll find them all.

const encryptNum = num => num.replace(/((\d[-\s.]?){4})(\d)([\s.-]?)(\d)([\s.-]?)(\d)([\s.-]?)(\d)([\s.-]?)(\d)([\s.-]?)(\d)/, '$1X$4X$6X$8X$10X$12X');
Collapse
exts profile image
Lamonte

Dart - not a fan of being creative.

String encryptNum(String phoneNum) {
  var regexp = RegExp(r"([0-9- .]+)");
  var result = regexp?.firstMatch(phoneNum)?.group(0);
  result = result?.replaceAll(new RegExp(r"(\.|-|\s)"), "");
  if(result.length == 10) {
    var val = StringBuffer();
    for(var idx = 0; idx < result.length; idx++) {
      if(idx < 4) {
        val.write(result[idx]);
      } else {
        val.write("X");
      }
      switch(idx) {
        case 2:
        case 5:
          val.write("-");
        break;
      }
    }
    return val.toString();
  }
  return null;
}
print(encryptNum("142 424 2142"));
print(encryptNum("142 424-2142"));
print(encryptNum("142 424.2142"));
print(encryptNum("142 4242142"));
print(encryptNum("142-424-2142"));
print(encryptNum("142-424 2142"));
print(encryptNum("142-424.2142"));
print(encryptNum("142-4242142"));
print(encryptNum("142.424 2142"));
print(encryptNum("142.424-2142"));
print(encryptNum("142.424.2142"));
print(encryptNum("142.4242142"));
print(encryptNum("142424-2142"));
print(encryptNum("142424.2142"));
print(encryptNum("142424 2142"));
print(encryptNum("1424242142"));
Collapse
viktordimitrievski profile image
Viktor

JavaScript solution:

const encryptNum = num => {
    num = num.replace(/[-,\s]/g,"");
    return `${num.substring(0,3)}-${num.substring(3,4)}XX-XXXX`
}
Collapse
cipharius profile image
Valts Liepiņš

Ruby

def encryptNum num
    num.reverse!.gsub(/\d/).with_index { |d, i| i < 6 ? 'X' : d }.reverse!
end
Collapse
toanleviettiki profile image
Toan Le Viet

My basic solution in JS: Reverse string then replace with 'X' and count to 6

Collapse
georgewl profile image
George W Langham

Didn't even know you can embed into comments, that's really cool.

Do you not feel that solution is overcomplicating it?