12
\$\begingroup\$

Introduction

Recently I was trying out one of the more obscure use-cases of a modern smartphone: Calling someone by number. While typing it in, I noticed that some of my phonebook entries were displayed, even though the number I was trying to call was different! After some experiments, I figured out why.

What it does

Every entry in the phone book is examined like this:

  • Split the entry by space into "words".
  • Check every word like so:
    • For every digit in the number...
    • Is the character of the word at the current index on the key with the digit at the current number?
  • If at least one such word exists, display this entry

Challenge

Emulate my smartphone's behaviour!
Take the list of names below and a numeric string as input. The format for the phonebook may be chosen freely. Assume the phone number to always match [0-9]* and all names to match [0-9a-zA-Z\s]+

You can expect entries consisting of ASCII characters with values between 32 and 126 (including both). Your program should handle any length of entry and words within, as well as a list of any size.

Output a filtered list.

Input and output order is not relevant.

Use the following phone keyboard:

  1  |  2  |  3   
     | abc | def
-----------------
  4  |  5  |  6   
 ghi | jkl | mno
-----------------
  7  |  8  |  9   
 pqr | tuv | wxy
  s  |     |  z
-----------------
     |  0  |     
     |     |     

Rules

  • This is , shortest code wins
  • No standard loopholes

Phone Book, values comma-seperated.

noodle9, Kevin Cruijssen, Arnauld, RGS, xnor, Bubbler, J42161217, Neil, petStorm, fireflame241, Dennis, Martin Ender, Leaky Nun, Lyxal, HighlyRadioactive, Dingus, math, Beefster, Razetime, my pronoun is monicareinstate, Dom Hastings, Dion

Test cases

Input: 0815
Output: []

Input: 731
Output: []

Input: 532596
Output: []

Input: 53259
Output: [Leaky Nun]

Input: 542
Output: [J42161217]

Input: 27
Output: [Kevin Cruijssen, Arnauld]

Input: 36
Output: [Martin Ender, Dom Hastings]

Input: 6
Output; [noodle9, Neil, Martin Ender, math, my pronoun is monicareinstate, Leaky Nun]

Input: 3
Output; [Dennis, Martin Ender, fireflame241, Dingus, Dion, Dom Hastings]
```
\$\endgroup\$
  • \$\begingroup\$ Add Dion to the phonebook \$\endgroup\$ – null Aug 28 at 11:39
  • 3
    \$\begingroup\$ I thought we were friends? 😁 \$\endgroup\$ – 640KB Aug 28 at 15:06
  • 1
    \$\begingroup\$ @640KB And I didn't even knew I was friends with mindoverflow ;) \$\endgroup\$ – Kevin Cruijssen Aug 28 at 15:27
  • \$\begingroup\$ Leaky Nun is missing in output for input = 6 i think \$\endgroup\$ – Manish Kundu Aug 28 at 16:14
  • 1
    \$\begingroup\$ Should 0 and 1 in the number be ignored, or do they prevent matches of anything? A test case for that would be good. \$\endgroup\$ – Jonah Aug 28 at 19:05

12 Answers 12

5
\$\begingroup\$

JavaScript (ES6), 102 bytes

Expects (book)(digits), where book is an array of strings and digits is an array of integers. Returns an array of strings.

b=>a=>b.filter(s=>eval(`/\\b[${a.map(i=>i+"01adgjmptw"[i]+"-"+"01cfilosvz"[i]).join`][`}]/i`).test(s))

Try it online!

How?

The sequence of digits is turned into a regular expression of the form:

/\b[Dx-y][Dx-y]...[Dx-y]/i

where D is the original digit and x-y is the letter range associated to it, or D-D for 0 or 1.

For instance, [2,7] becomes /\b[2a-c][7p-s]/i.

We walk through the phone book and keep only the names that are matched by this regular expression.

Commented

b => a =>             // b[] = book, a[] = integer sequence
b.filter(s =>         // for each string s in b[]:
  eval(               //   evaluate as JS code:
    "/\\b" +          //     regexp delimiter, followed by \b
    "[" +             //     followed by the first '['
    a.map(i =>        //     for each integer i in a[]:
      i +             //       append i
      "01adgjmptw"[i] //       append the first range character
      + "-" +         //       append a '-'
      "01cfilosvz"[i] //       append the second range character
    ).join`][` +      //     end of map(); join with ']['
    "]/i"             //     append the last ']' and '/i'
  )                   //   end of eval()
  .test(s)            //   keep s if it's matching the above regular expression
)                     // end of filter()
| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Not that it matters for the byte-count, but you could get rid of the unprintables ␡ by just using 01 instead of \x7f\x7f in both strings (like I did in my Java port of your answer). \$\endgroup\$ – Kevin Cruijssen Aug 28 at 14:58
  • \$\begingroup\$ @KevinCruijssen That's a bit clearer that way indeed. Thank you for the suggestion. \$\endgroup\$ – Arnauld Aug 28 at 15:22
5
\$\begingroup\$

Python 3, 96 bytes

lambda n,p:[s for s in p if' '+n in''.join([i,'%i'%min(ord(i)%32/3.2+2,9)][i>'9']for i in' '+s)]

Try it online!

Also works in Python 2

ord(i)%32 converts both upper and lower case characters to the range (1,2,3,...,24,25,26). Division by 3.2 converts this list to 00011122233344455556667778. Adding 2 to the list and using min to convert the last digit into 9, completes the character mapping 22233344455566677778889999.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Very nice approach! You can save 2 bytes in Python2 by using the backtick to convert number to string. \$\endgroup\$ – Surculose Sputum Aug 31 at 15:02
  • \$\begingroup\$ @SurculoseSputum Thanks! Sadly, it does not yield anything with my new approach. \$\endgroup\$ – Jitse Sep 1 at 14:14
3
\$\begingroup\$

05AB1E, 34 28 20 bytes

ʒl1úA9L¦3и79ªS{‡I1úå

-8 bytes by porting @SurculoseSputum's Python 2 answer, so make sure to upvote him as well!

First input is the list of contacts, second is the integer.

Try it online or verify all test cases.

Original 34 28 bytes answer:

ʒlð¡εUεA•Ê_¢•6в<£yè«XNèå}P}à

First input is the list of contacts, second is the integer.

Try it online (the test case that results in my own name, I'm honored ^-^) or verify all test cases.

Explanation:

ʒ                # Filter the (implicit) input-list of contacts by:
 l               #  Convert the name to lowercase
  1ú             #  Pad the string with a single leading space
    A            #  Push the lowercase alphabet
     9L          #  Push a list in the range [1,9]
       ¦         #  Remove the first item to make the range [2,9]
        3и       #  Repeat the list 3 times: [1,2,3,4,5,6,7,8,9,1,2,3,...,9]
          79ª    #  Append 79 to the list: [2,3,4,5,6,7,8,9,2,3,4,...,9,79]
             S   #  Convert the list to a flattened list of digits:
                 #   [2,3,4,5,6,7,8,9,2,3,4,...,9,7,9]
              {  #  Sort the list: [2,2,2,3,3,3,...,8,8,8,9,9,9,9]
               ‡ #  Transliterate the alphabet to these digits in the contact-string
 I               #  Push the second input-integer
  1ú             #  Pad it with a single leading space as well
    å            #  And check if it's a substring of the transliterated contact-string
                 # (after which the filtered list of contacts is output implicitly)
ʒ                # Filter the (implicit) input-list of contacts by:
 l               #  Convert the name to lowercase
  ð¡             #  Split it on spaces to a list of words
                 #  (NOTE: `#` can't be used here, because it won't result in a list for
                 #   strings without spaces)
    ε            #  Map each word to:
     U           #   Pop the word and store it in variable `X`
     ε           #   Map the digits of the (implicit) input-integer to:
      A          #    Push the lowercase alphabet
       •Ê_¢•     #    Push compressed integer 13101041
            6в   #    Convert it to base-6 as list: [1,1,4,4,4,4,4,5,4,5]
              <  #    Decrease each by 1: [0,0,3,3,3,3,3,4,3,4]
               £ #    Split the alphabet into parts of that size: 
                 #     ["","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"]
      yè         #    Index the current digit into this list
        «        #    Append the string to the current digit
         X       #    Push the current word `X`
          Nè     #    Index the map-index into it
            å    #    Check if this character is in the string (i.e. "abc2" and "c" → 1)
     }P          #   After the map: check if all digits were truthy
    }à           #  After the map: check if this was truthy for any word
                 # (after which the filtered list of contacts is output implicitly)

See this 05AB1E tip of mine (sections How to compress large integers? and How to compress integer lists?) to understand why •Ê_¢• is 13101041 and •Ê_¢•6в is [1,1,4,4,4,4,4,5,4,5].

| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

Python 2, 129 111 108 bytes

lambda n,S:[s for s in S if" "+n in"".join([`(ord(c)+(c<"S")-(c>"Y"))/3-20`,c][c<"A"]for c in" "+s.upper())]

Try it online!

A function that takes in a phone number string and a list of names, and returns the list of matched names.

For each name, converts it to its digit string equivalent, then returns the name if the phone number is found inside that name's digit string. To make sure that the phone number only matches the start of a word in name, before matching, a space is prepended in front of the phone number and the name.

Example:

name : "Kevin Cruijssen"    -> " 53846 278457736" (notice the space at the start)
phone: "27"                 -> " 27"
" 27" in " 53846 278457736" -> True
| improve this answer | |
\$\endgroup\$
3
\$\begingroup\$

R, 103 102 bytes

Edit: -1 byte by prepending 0 (zero followed by space) to the contact name, instead of just (space), since the zero without a preceding space can itself never be matched

function(n,l,`~`=toString)l[grepl(paste("",n),chartr(~letters,~c(7:31%/%3.13,9),paste(0,tolower(l))))]

Try it online!

Same approach as Surculose Sputum's answer: convert phonebook to space-separated numbers & search for match.

Commented code:

find_contact=
function(n,l                # n=number, l=list of contacts
 ,`~`=toString)             # ~=alias to toString function
 l[                         # return all entries in the list of contacts...
  grepl(                    # ...for which there is a match for...
   paste("",n),             # ...the number with prepended space...
   chartr(                  # ...in the list made by swapping all...
    ~letters,               # ...lowercase letters for...
    ~c(7:31%/%3.13,9),      # ...digits from 2 to 9 in groups of 3 (or 4 for 7 and 9)... 
    paste("",tolower(l))    # ...in the lowercase list of contacts with prepended spaces
  ))]
| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Retina, 72 bytes

~(1G`
.
[$&$&-$&]
T`2-9`a\dgjm\pt\w`.-
T`2-9`cfi\l\os\vz`-.
^
Gi`(?<=\W)

Try it online! Another port of @Arnauld's answer. Takes the first line as the digits and the remaining lines as the phone book. Explanation:

~(

Execute the rest of the program, then use the output as a program and execute that on the original input.

1G`

Keep only the line with the digits.

.
[$&$&-$&]

Turn each digit into a character class.

T`2-9`a\dgjm\pt\w`.-
T`2-9`cfi\l\os\vz`-.

Adjust the first and last characters of the range to the appropriate letters.

^
Gi`(?<=\W)

Prefix a Retina instruction to keep only lines matching those characters (case insensitively) when prefixed by a non-letter (therefore excluding the line of digits).

The result of the inner script looks something like this:

Gi`(?<=\W)[6m-o]

This is a Retina program to keep lines that match any of the characters 6mno after a non-word character. Note that I can't include the non-word character in the match as that would cause the previous line to be included if the newline is the character in question.

| improve this answer | |
\$\endgroup\$
2
\$\begingroup\$

Jelly, 26 bytes

Œl>Ɱ“®K¿ʂ⁹FD»So⁸V€Ƥċ
ḲçƇ¥Ƈ

A dyadic Link accepting a list of the names on the left and a list of digits on the right which yields a list of names.

Try it online! (Footer formats the list, which when run as a full program gets implicitly smashed)

How?

Œl>Ɱ“®K¿ʂ⁹FD»So⁸V€Ƥċ - Link 1: list of characters, word; list of integers, key-digits
Œl                   - lower-case
    “®K¿ʂ⁹FD»        - compressed string "AAcfilosv" ("AA"+"c"+"filos"+"v")
   Ɱ                 - map with:                       [ ...mmm filos :D ]
  >                  -   greater than?
             S       - sum
               ⁸     - chain's left argument, word
              o      - OR (vectorises) - i.e. replace 0s with word's digit characters
                  Ƥ  - for prefixes:
                V€   -   evaluate each as Jelly - i.e. cast any chars to ints
                   ċ - count (occurrence of key-digits) -> 1 if a prefix, else 0

ḲçƇ¥Ƈ - Main Link: list of lists of characters, names; list of integers, key-digits
    Ƈ - filter keep (names) for which:
   ¥  -   last two links as a dyad - i.e. f(name, key-digits):
Ḳ     -     split (name) at spaces
  Ƈ   -     filter keep (words) for which:
 ç    -       call last Link (1) as a dyad - i.e. g(word, key-digits)
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Java 8, 172 bytes

C->n->C.filter(c->{var s="(?i)";for(int i:n)s+="["+"01adgjmptw".charAt(i)+"-"+"01cfilosvz".charAt(i)+i+"]";var r=0>1;for(var w:c.split(" "))r|=w.matches(s+".*");return r;})

Port of @Arnauld's JavaScript (ES6) answer, so make sure to upvote him as well!

Try it online.

Explanation:

C->n->              // Method with String-Stream & Integer-array parameters and String-Stream return-type
  C.filter(c->{     //  Filter the input String-Stream by:
    var s="(?i)";   //   Create a regex-String, starting at "(?i)"
    for(int i:n)    //   Loop over each digit of the input integer-array:
      s+=           //    Append the following to the regex-String:
         "["        //     An opening square bracket
         +"01adgjmptw".charAt(i)
                    //    Appended with the `i`'th character of "01adgjmptw"
         +"-"       //    Appended with a "-"
         +"01cfilosvz".charAt(i)
                    //    Appended with the `i`'th character of "01cfilosvz"
         +i         //    Appended with digit `i` itself
         +"]";      //    Appended with a closing square bracket
    var r=0>1;      //  Boolean `r`, starting at false
    for(var w:c.split(" "))
                    //  Split the current String by spaces, and loop over each word:
      r|=           //   Change the boolean to true if the following is true:
         w.matches( //    Check if the current word regex-matches:
           s        //     The regex-String we created earlier
           +".*");  //     Appended with ".*"
    return r;})     //  After the loop, return this boolean `r` to filter on

Regex explanation:

The String#matches method in Java implicitly adds a leading ^ and trailing $ to match the entire String.

I.e. input [5,4,2] would result in the following regex:

^(?i)[a-c2][p-s7].*$
^                  $  # Match the entire string
 (?i)                 # Match case-insensitive
     [a-c             # A character in the range a-c (thus in "abc")
         2]           # or a 2 (thus in "abc2")
           [p-s       # Followed by a character in the range p-s (thus in "pqrs")
               7]     # or a 7 (thus in "pqrs7")
                 .*   # Followed by any amount of optional trailing characters
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Python 3, 242 239 237 bytes

def f(b,n,k=[]):
 x=[[k for k in j]for j in'0 1 abc2 def3 ghi4 jkl5 mno6 pqrs7 tuv8 wxyz9'.split()]
 for i in n:k=k and[p+j for p in k for j in x[i]]or x[i]
 return[u for u in b if sum(j==r[:len(j)]for j in k for r in u.lower().split())]

Try it online!

Explanation: Computes all possible strings that can be formed with the number and checks if any word in a name starts with any of those strings.

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Charcoal, 43 42 bytes

SθWS⊞υιΦυ№E⪪↥ι ⭆✂λ⁰Lθ¹⎇№ανΣE  CFILOSV›νπνθ

Try it online! Link is to verbose version of code. Takes input as the number and a newline-terminated list of phone book entries. Edit: Saved 1 byte by copying @JonathanAllan's digit conversion algorithm, which means that I get to use the p variable again. Explanation:

SθWS⊞υι

Input the number and the entries. (These bytes could be removed by substituting a more cumbersome input format.)

 υ                                  List of phonebook entries
Φ                                   Filtered where
      ι                             Current entry
     ↥                              Uppercased
    ⪪                               Split on spaces
   E                                Map over words
          λ                         Current word
         ✂ ⁰Lθ¹                     Sliced to input digits length
        ⭆                           Map over characters and join
               ⎇                    Ternary
                 α                  Uppercase alphabet
                №                   Count of (i.e. contains)
                  ν                 Current character
                       CFILOSV      Literal string `  CFILOSV`
                    E               Map over characters
                               ν    Word character
                              ›     Is greater than
                                π   Inner character
                   Σ                Take the sum
                                 ν  Else current character
  №                                 Count of (i.e. contains)
                                  θ Input digits
                                    Implicitly print
| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Rust, 158 154 bytes

|a,b|b.filter(move|x|x.split(|&b|b<33).any(|w|(0..).zip(a).all(|(j,&i)|j<w.len()&&(b"@CFILOSVZ".iter().fold(1,|a,&b|a+(b<w[j]&95)as u8)==i||48+i==w[j]))))

Try it online!

The code is a bit of a mess, with a sprinkle of .iter(), & and move here and there. The strings are represented as &[u8]s, as are the numbers pressed. Rust doesn't have regexes in its standard library, so I manually find the digit for each letter from the array b"@CFILOSVZ". Case insensitivity is performed by bitmasking with 95 which is 0x5f = 0x7f - 0x20. This transforms the lowercase chars to uppercase.

Some bytes were saved in the edit by switching from b==32 to b<33 and using (0..).zip() and indexing instead of w.iter().zip().

| improve this answer | |
\$\endgroup\$
1
\$\begingroup\$

Scala, 118 bytes

n=>_.filter(_ split " "exists(_.matches("(?i)"+n.map(i=>s"[$i${"01adgjmptw"(i)}-${"01cfilosvz"(i)}]").mkString+".*")))

Try it online

A port of @Arnauld's JavaScript (ES6) answer.

A curried lambda of type Seq[Int]=>Seq[String]=>Seq[String].

| improve this answer | |
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.