Daily Challenge #50 - Number Neighbor

twitter logo ・1 min read

Daily Challenge (137 Part Series)

1) Daily Challenge #1 - String Peeler 2) Daily Challenge #2 - String Diamond 3 ... 135 3) Daily Challenge #3 - Vowel Counter 4) Daily Challenge #4 - Checkbook Balancing 5) Daily Challenge #5 - Ten Minute Walk 6) Daily Challenge #6 - Grandma and her friends 7) Daily Challenge #7 - Factorial Decomposition 8) Daily Challenge #8 - Scrabble Word Calculator 9) Daily Challenge #9 - What's Your Number? 10) Daily Challenge #10 - Calculator 11) Daily Challenge #11 - Cubic Numbers 12) Daily Challenge #12 - Next Larger Number 13) Daily Challenge #13 - Twice Linear 14) Daily Challenge #14 - Square into Squares 15) Daily Challenge #15 - Stop gninnipS My sdroW! 16) Daily Challenge #16 - Number of People on the Bus 17) Daily Challenge #17 - Double Trouble 18) Daily Challenge #18 - Triple Trouble 19) Daily Challenge #19 - Turn numbers into words 20) Daily Challenge Post #20 - Number Check 21) Daily Challenge #21 - Human Readable Time 22) Daily Challenge #22 - Simple Pig Latin 23) Daily Challenge #23 - Morse Code Decoder 24) Daily Challenge #24 - Shortest Step 25) Daily Challenge #25 - Double Cola 26) Daily Challenge #26 - Ranking Position 27) Daily Challenge #27 - Unlucky Days 28) Daily Challenge #28 - Kill the Monster! 29) Daily Challenge #29 - Xs and Os 30) Daily Challenge #30 - What is the price? 31) Daily Challenge #31 - Count IPv4 Addresses 32) Daily Challenge #32 - Hide Phone Numbers 33) Daily Challenge #33 - Did you mean...? 34) Daily Challenge #34 - WeIrD StRiNg CaSe 35) Daily Challenge #35 - Find the Outlier 36) Daily Challenge #36 - Let's go for a run! 37) Daily Challenge #37 - Name Swap 38) Daily Challenge #38 - Middle Name 39) Daily Challenge #39 - Virus 40) Daily Challenge #40 - Counting Sheep 41) Daily Challenge #41 - Greed is Good 42) Daily Challenge #42 - Caesar Cipher 43) Daily Challenge #43 - Boardgame Fight Resolver 44) Daily Challenge #44 - Mexican Wave 45) Daily Challenge #45 - Change Machine 46) Daily Challenge #46 - ??? 47) Daily Challenge #47 - Alphabets 48) Daily Challenge #48 - Facebook Likes 49) Daily Challenge #49 - Dollars and Cents 50) Daily Challenge #50 - Number Neighbor 51) Daily Challenge #51 - Valid Curly Braces 52) Daily Challenge #52 - Building a Pyramid 53) Daily Challenge #53 - Faro Shuffle 54) Daily Challenge #54 - What century is it? 55) Daily Challenge #55 - Building a Pile of Cubes 56) Daily Challenge #56 - Coffee Shop 57) Daily Challenge #57 - BMI Calculator 58) Daily Challenge #58 - Smelting Iron Ingots 59) Daily Challenge #59 - Snail Sort 60) Daily Challenge #60 - Find the Missing Letter 61) Daily Challenge #61 - Evolution Rate 62) Daily Challenge #62 - Josephus Survivor 63) Daily Challenge #63- Two Sum 64) Daily Challenge #64- Drying Potatoes 65) Daily Challenge #65- A Disguised Sequence 66) Daily Challenge #66- Friend List 67) Daily Challenge #67- Phone Directory 68) Daily Challenge #68 - Grade Book 69) Daily Challenge #69 - Going to the Cinema 70) Daily Challenge #70 - Pole Vault Competition Results 71) Daily Challenge #71 - See you next Happy Year 72) Daily Challenge #72 - Matrix Shift 73) Daily Challenge #73 - ATM Heist 74) Daily Challenge #74 - Free Pizza 75) Daily Challenge #75 - Set Alarm 76) Daily Challenge #76 - Bingo! (or not...) 77) Daily Challenge #77 - Bird Mountain 78) Daily Challenge #78 - Number of Proper Fractions with Denominator d 79) Daily Challenge #79 - Connect Four 80) Daily Challenge #80 - Longest Vowel Change 81) Daily Challenge #81 - Even or Odd 82) Daily Challenge #82 - English Beggars 83) Daily Challenge #83 - Deodorant Evaporator 84) Daily Challenge #84 - Third Angle of a Triangle 85) Daily Challenge #85 - Unwanted Dollars 86) Daily Challenge #86 - Wouldn't, not Would. 87) Daily Challenge #87 - Pony Express 88) Daily Challenge #88 - Recursive Ninjas 89) Daily Challenge #89 - Extract domain name from URL 90) Daily Challenge #90 - One Step at a Time 91) Daily Challenge #91 - Bananas 92) Daily Challenge #92 - Boggle Board 93) Daily Challenge #93 - Range Extraction 94) Daily Challenge #94 - Last Digit 95) Daily Challenge #95 - CamelCase Method 96) Daily Challenge #96 - Easter Egg Crush Test 97) Daily Challenge #97 - Greed is Good 98) Daily Challenge #98 - Make a Spiral 99) Daily Challenge #99 - Balance the Scales 100) Daily Challenge #100 - Round Up 101) Daily Challenge #101 - Parentheses Generator 102) Daily Challenge #102 - Pentabonacci 103) Daily Challenge #103 - Simple Symbols 104) Daily Challenge #104 - Matrixify 105) Daily Challenge #105 - High-Sum Matrix Drop 106) Daily Challenge #106 - Average Fuel Consumption 107) Daily Challenge #107 - Escape the Mines 108) Daily Challenge #108 - Find the Counterfeit Coin 109) Daily Challenge #109 - Decorate with Wallpaper 110) Daily Challenge #110 - Love VS. Friendship 111) Daily Challenge #111 - 99 Bottles of Beer 112) Daily Challenge #112 - Functions of Integers on the Cartesian Plane 113) Daily Challenge #113 - Iterative Rotation Cipher 114) Daily Challenge #114 - Speed Control 115) Daily Challenge #115 - Look and Say Sequence 116) Daily Challenge #116 - Shortest Knight Path 117) Daily Challenge #117 - MinMinMax 118) Daily Challenge #118 - Reversing a Process 119) Daily Challenge #119 - Adding Big Numbers 120) Daily Challenge #120 - Growth of a Population 121) Daily Challenge #121 - Who has the most money? 122) Daily Challenge #122 - Clockwise Spirals 123) Daily Challenge #123 - Curry me Softly 124) Daily Challenge #124 - Middle Me 125) Daily Challenge #125 - 23 Matches or More 126) Daily Challenge #126 - The Supermarket Line 127) Daily Challenge #127 - Playing with Passphrases 128) Daily Challenge #128 - Blackjack Scorer 129) Daily Challenge #129 - Clay Pigeon Shooting 130) Daily Challenge #130 - GCD Sum 131) Daily Challenge #131 - Remove Anchor from URL 132) Daily Challenge #132 - Is my friend cheating? 133) Daily Challenge #133 - Suitcase Packing 134) Daily Challenge #134 - Rice and Chessboard Problem 135) Daily Challenge #135 - The Wide Mouthed Frog! 136) Daily Challenge #136 - The Deaf Rats of Hamelin 137) Daily Challenge #137 - Help the Bookseller

Given a phone number of integer length N (1 ≤ N ≤ 10) as a number string (e.g. 555-555-5555 would be passed as "5555555555"), return an array of all phone numbers as number strings that would be considered neighbors of that phone number (which is any numbers ±1).

For example: someone with the phone number 555-555-5555 has neighbors 555-555-5554 and 555-555-5556.


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

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

twitter logo DISCUSS (19)
markdown guide
 

Huh, this one was harder than I expected it to be... that probably means I missed something 🤣

Also, TIL that setting a character of a string in JS with square brackets doesn't throw an error - but also doesn't do anything! example:

let myStr = "abc123"
myStr[0] = 'X'
// myStr is unchanged here!

Solution in javascript:


const numberNeighbor = (number) => {
  return [...number].reduce((arr, num, i) => {
    num = Number(num)

    if(num - 1 >= 0) {
      arr.push([ 
        number.slice(0, i), 
        num - 1, 
        number.slice(i+1) 
      ].join(""))
    }

    if(num + 1 <= 9) {
      arr.push([ 
        number.slice(0, i), 
        num + 1, 
        number.slice(i+1) 
      ].join(""))
    }

    return arr
  }, [])
}
 

Strings in javascript are immutable, so even if you change a character using the [] notation, it always returns a new string.

This instead work as you expected

// The string is transformed into an Array
const arrString = [..."abc123"]
arrString[0] = "X"
// arrString is now ["X", "b", "c", "1", "2", "3"]
 

Makes sense - thanks! (forgot (or never really knew?) that strings in javascript were immutable)

 
// areaCode :: String -> String
const areaCode = str => str.substring(0,3);
// threeCode :: String -> String
const threeCode = str => str.substring(3,6);
// fourCode :: String -> String
const fourCode = str => str.substring(6);
// formatPhone :: String -> String
const formatPhone = str => `${areaCode(str)}-${threeCode(str)}-${fourCode(str)}`;

// getNeighbors :: String -> [Number] -> [String]
const getNeighbors = number => [parseInt(number) + 1, parseInt(number) - 1]
  .map(neighbor => formatPhone(`${neighbor}`));

Attempting to learn and understand more functional paradigms. Is this right way to solve this problem functionally?

 

Way to go buddy!

From my perspective, to format the phone number in 3 segments, I would have used this instead of the 3 calls you made.

"use strict"

const phoneNumber = "123456789"

const formatPhoneNumber = (string, glue) => string.match(/.{1,3}/g).join(glue)

console.log(formatPhoneNumber(phoneNumber, "-")) // 123-456-789
console.log(formatPhoneNumber(phoneNumber, " ")) // 123 456 789
console.log(formatPhoneNumber(phoneNumber, ".")) // 123.456.789

This leverage the regular expressions to match three characters at most. I know regular expressions is not the easiest thing to use but in this case it can be quite handy. As it returns an array, joining the items will produce a string back so the signature of the function can be written as follow in a purely functional programming language like PureScript.

formatPhoneNumber :: String -> String -> String

Unfortunately, currying (partially applying parameters) is not built-in in JavaScript. You could implement your own curry function and use it on all your future function definitions. But this would be overkill for this challenge. But if you would ask me how I would implement a currying function in JavaScript, here is my take.

"use strict"

function curry(callable, ...initialParameters) {
  if (typeof callable !== "function") {
    throw new TypeError("Function expected as first argument")
  }

  return function(...additionalParameters) {
    const parameters = [...initialParameters, ...additionalParameters]

    if (parameters.length >= callable.length) {
      return callable(...parameters)
    }

    return curry(callable, ...parameters)
  }
}

// format :: String -> Number -> String -> String
const format = curry(function(glue, segments, phone) {
  return phone.match(new RegExp(`.{1,${segments}}`, "g")).join(glue)
})

// usFormat :: String -> String
const usFormat = format("-", 3)

// frenchFormat :: String -> String
const frenchFormat = format(".", 2)

console.log(usFormat("123456789")) // 123-456-789
console.log(frenchFormat("1234567890")) // 12.34.56.78.90

Last tip, when creating a function, try to put the data structure at the end, that way you can construct partial application of your function without pain, even in JavaScript. In this case, this means puting the phone number to format at the very end of the parameter's list in your function definition. Hope that helps you in your functional programing journey pal.

 

Hey thanks for the response I will look at regexes next time for this kind of problem. Also I thought currying was supported in ES6?
via

const f = a => b=> a + b

What you did is indeed currying but it is not transparent in the sense that if you want to pass all three arguments at once you will have to make three calls. The curry implementation allows you to either pass all arguments at once like a regular function call or partially calling the function. I kinda dislike calling my function multiple times and the only two reasons are aesthetic and the fact that it is less natural to define functions like that but arrow functions syntax makes it a little bit cleaner.

 

Here goes some ugly code!

f=a=>{p=parseInt,l=(''+a).length,i=0,r=[a];while(i<l){r.push(...[('0'+(p(a)-(10**i))).slice(-l),''+(p(a)+(10**i))]);++i}return r}

It works like that:

  • Take the input's length
  • Add and subtract 10n (where n goes from 0 to the input's length - 1) to the input number
  • Add a trailing 0 if necessary
  • Return the built array!
f('5555555555').join('\n');
"5555555555
5555555554
5555555556
5555555545
5555555565
5555555455
5555555655
5555554555
5555556555
5555545555
5555565555
5555455555
5555655555
5554555555
5556555555
5545555555
5565555555
5455555555
5655555555
4555555555
6555555555"
 

One line JS generate all possible neighbor number for any position.

const allNumberNeighbor = number => [...number]
  .map(Number)
  .flatMap((n, i) => Number(n) && [
    n + 1 <= 9 && Object.values({ ...number, [i]: n + 1 }),
    n - 1 >= 0 && Object.values({ ...number, [i]: n - 1 }),
  ])
  .filter(Boolean)
  .map(n => n.join(''));

allNumberNeighbor('555-555-555');
// return
/*
 [
  '655-555-555',
  '455-555-555',
  '565-555-555',
  '545-555-555',
  '556-555-555',
  '554-555-555',
  '555-655-555',
  '555-455-555',
  '555-565-555',
  '555-545-555',
  '555-556-555',
  '555-554-555',
  '555-555-655',
  '555-555-455',
  '555-555-565',
  '555-555-545',
  '555-555-556',
  '555-555-554',
 ];
*/
 

any numbers ±1

🤔
Most people interpreted that as only a single number could vary at a time, others took the example too literally as the full number +- 1...

So I thought...

What if any numberS (plural) means any number of digits can vary...?

const neighbourSingle = (num) => [...new Set([ clamp(num-1), num, clamp(num+1) ])];
const clamp = (num) => Math.min(Math.max(num, 0), 9);
/*
neighbourSingle(5) === [4, 5, 6]
neighbourSingle(0) === [0, 1]
*/

const neighbours = (phoneString) => {
    return phoneString.split("").reduce((a,b) => a.flatMap(c=> neighbourSingle(parseInt(b)).map(d=> c+d)) ,[""])
}
/*
neighbours("55")
(9) ["44", "45", "46", "54", "55", "56", "64", "65", "66"]

neighbours("555")
(27) ["444", "445", "446", "454", "455", "456", "464", "465", "466", "544", "545", "546", "554", "555", "556", "564", "565", "566", "644", "645", "646", "654", "655", "656", "664", "665", "666"]

neighbours("5555555555")
(59049) ["4444444444", "4444444445", "4444444446", "4444444454", "4444444455", "4444444456", "4444444464", "4444444465", "4444444466", "4444444544", "4444444545", "4444444546", "4444444554", "4444444555", "4444444556", "4444444564", "4444444565", "4444444566", "4444444644", "4444444645", "4444444646", "4444444654", "4444444655", "4444444656", "4444444664", "4444444665", "4444444666", "4444445444", "4444445445", "4444445446", "4444445454", "4444445455", "4444445456", "4444445464", "4444445465", "4444445466", "4444445544", "4444445545", "4444445546", "4444445554", "4444445555", "4444445556", "4444445564", "4444445565", "4444445566", "4444445644", "4444445645", "4444445646", "4444445654", "4444445655", "4444445656", "4444445664", "4444445665", "4444445666", "4444446444", "4444446445", "4444446446", "4444446454", "4444446455", "4444446456", "4444446464", "4444446465", "4444446466", "4444446544", "4444446545", "4444446546", "4444446554", "4444446555", "4444446556", "4444446564", "4444446565", "4444446566", "4444446644", "4444446645", "4444446646", "4444446654", "4444446655", "4444446656", "4444446664", "4444446665", "4444446666", "4444454444", "4444454445", "4444454446", "4444454454", "4444454455", "4444454456", "4444454464", "4444454465", "4444454466", "4444454544", "4444454545", "4444454546", "4444454554", "4444454555", "4444454556", "4444454564", "4444454565", "4444454566", "4444454644", …]
*/
 

My take at the challenge written in Elm.

computeNeighbors : String -> Maybe (List Int)
computeNeighbors phoneNumber = 
  case String.toInt phoneNumber of
    Just integerPhoneNumber ->
      Just [integerPhoneNumber + 1, integerPhoneNumber - 1]
    Nothing ->
      Nothing

Try it online.

 

This was a little harder, at least to me it was.

phone_number.go

package phonenumbers

// PhoneNeighbors returns all the phone numbers that are adjacent to the given phone number
func PhoneNeighbors(phone string) []string {
    numbers := []string{}

    if phone == "" {
        return numbers
    }

    runes := []rune(phone)

    for i, r := range runes {
        // Copy the phone number so we can replace the current digit with its neighbors during the loop
        instance := append(runes[:0:0], runes...)

        // Get the neighbors of this digit
        n := neighbors(r)

        // Loop over the neighbors
        for _, num := range n {
            // Replace the digit at this index with the neighbor and append the result to the slice
            instance[i] = num
            numbers = append(numbers, string(instance))
        }
    }

    return numbers
}

func neighbors(number rune) []rune {
    switch number {
    case '0':
        return []rune{'8'}
    case '1':
        return []rune{'2', '4'}
    case '2':
        return []rune{'1', '3', '5'}
    case '3':
        return []rune{'2', '6'}
    case '4':
        return []rune{'1', '5', '7'}
    case '5':
        return []rune{'2', '4', '6', '8'}
    case '6':
        return []rune{'3', '5', '9'}
    case '7':
        return []rune{'4', '8'}
    case '8':
        return []rune{'0', '5', '7', '9'}
    case '9':
        return []rune{'6', '8'}
    default:
        return []rune{}
    }
}

phone_number_test.go

package phonenumbers

import "testing"

var testCases = []struct {
    description string
    input       string
    expected    []string
}{
    {
        "empty string",
        "",
        []string{},
    },
    {
        "single digit",
        "5",
        []string{"2", "4", "6", "8"},
    },
    {
        "many digits",
        "4531",
        []string{
            "1531",
            "5531",
            "7531",
            "4231",
            "4431",
            "4631",
            "4831",
            "4521",
            "4561",
            "4532",
            "4534",
        },
    },
}

func equal(result []string, expected []string) bool {
    if len(result) != len(expected) {
        return false
    }

    for i := range result {
        if result[i] != expected[i] {
            return false
        }
    }

    return true
}

func TestPhoneNeighbors(t *testing.T) {
    for _, test := range testCases {
        if result := PhoneNeighbors(test.input); !equal(result, test.expected) {
            t.Fatalf("FAIL: %s - PhoneNeighbors(%s): %v - expected: %v", test.description, test.input, result, test.expected)
        }
        t.Logf("PASS: %s", test.description)
    }
}

 

I liked the "any number can differ by 1" approach more - it's a but more work :)

Rust:

pub fn number_neighbors(number: u32) -> Vec<u32> {
    let len = number.to_string().len() as u32;
    (0..len)
        .map(|delta| 10u32.pow(delta))
        .flat_map(|delta| vec![number - delta, number + delta])
        .collect()
}

And a test (I usually omit them from my submissions, but I think they have a place here):

#[test]                                                                   
fn test_number_neighbors() {                                              
    assert_eq!(vec![221, 223, 212, 232, 122, 322], number_neighbors(222));
}                                                                         
 

Simple ruby solution(with edge cases):

def neighbors str
  num = str.to_i
  res = case num
  when 0
    [num + 1]
  when 9999999999
    [num - 1]
  when -Float::INFINITY..0, 9999999999..Float::INFINITY
    raise 'Not a phone number'
  else
    [num - 1, num + 1]
  end

  res.map(&:to_s)
end
 

Python one-liner to the rescue again. (Horribly formatted this time tho)

f = lambda x: print(*[x[:len(x)-1]+str(int(x[len(x)-1])+1), x[:len(x)-1]+str(int(x[len(x)-1])-1)])

>>> f('555-555-5555')
555-555-5556 555-555-5554
 

My take on this challenge with JS

const neighbors = number => {
    const format = n => {
        n = `${n}`.padStart(10, '0');
        return `${`${n}`.substr(0,3)}-${`${n}`.substr(3, 3)}-${`${n}`.substr(6, 4)}`;
    };

    const nArray = [...number].reduce((t, d, i) => {
        const n1 = Number(number) + Math.pow(10, i);
        const n2 = number - Math.pow(10, i);

        t.push(format(n1));
        t.push(format(n2));

        return t;
    }, [])

    return nArray;
}

/**

neighbors("5555555555");

[
  "555-555-5556",
  "555-555-5554",
  "555-555-5565",
  "555-555-5545",
  "555-555-5655",
  "555-555-5455",
  "555-555-6555",
  "555-555-4555",
  "555-556-5555",
  "555-554-5555",
  "555-565-5555",
  "555-545-5555",
  "555-655-5555",
  "555-455-5555",
  "556-555-5555",
  "554-555-5555",
  "565-555-5555",
  "545-555-5555",
  "655-555-5555",
  "455-555-5555"
]

**/
 

My solution in js

const numberNeighbour = (num) => {
  const neighbours = [];
  num.split('').forEach((value, index) => {
    if(parseInt(value) - 1 > 0) {
      let prevNum = num.split('');
      prevNum[index] = parseInt(value) - 1;
      prevNum = prevNum.join('');
      neighbours.push(prevNum.slice(0, 3) + "-" + prevNum.slice(3, 6) + "-" + prevNum.slice(6));
    }
    if(parseInt(value) + 1 < 10) {
      let nextNum = num.split('');
      nextNum[index] = parseInt(value) + 1;
      nextNum = nextNum.join('');
      neighbours.push(nextNum.slice(0, 3) + "-" + nextNum.slice(3, 6) + "-" + nextNum.slice(6));
    }
  });
  return neighbours;
};

numberNeighbour('5555555555');
/*[
"455-555-5555",
"655-555-5555",
"545-555-5555",
"565-555-5555",
"554-555-5555",
"556-555-5555",
"555-455-5555",
"555-655-5555",
"555-545-5555",
"555-565-5555",
"555-554-5555",
"555-556-5555",
"555-555-4555",
"555-555-6555",
"555-555-5455",
"555-555-5655",
"555-555-5545",
"555-555-5565",
"555-555-5554",
"555-555-5556"
]*/
numberNeighbour('1111111111');
/*[
"211-111-1111",
"121-111-1111",
"112-111-1111",
"111-211-1111",
"111-121-1111",
"111-112-1111",
"111-111-2111",
"111-111-1211",
"111-111-1121",
"111-111-1112"
]*/
numberNeighbour('9999999999');
/*[
"899-999-9999",
"989-999-9999",
"998-999-9999",
"999-899-9999",
"999-989-9999",
"999-998-9999",
"999-999-8999",
"999-999-9899",
"999-999-9989",
"999-999-9998"
]*/
 
const neighborNumbers = number => [`${Number(number)-1}`, `${Number(number)+1}`];

neighborsNumbers("5555555555") // ["5555555554", "5555555556"]
 

So... something like this?

const neighbours = str => [ (parseInt(str) + 1).toString(), parseInt(str) - 1).toString() ]
Classic DEV Post from Oct 15

So meetup.com is going to charge attendees in future - what's next for event organizers?

So meetup.com is going to charge attendees in future - what's next for event organizers?

dev.to staff profile image
The hardworking team behind dev.to ❤️