Garbled Phone Numbers

Python 2, 314 307 274 bytes

lambda s:g(*''.join(q<n<"["and`(int(n,36)-4-(n>"R")-(n>"Y"))//3`or n for n in s).split(" / "))
def g(a,b,s=str.startswith):
 if b:c,d,e,f=a[0],a[1:],b[0],b[1:];b=(c==e and[c,q][c=="_"]or"_"in c+e and min(c,e)or[q,c,e][s(f,q)-s(d,q)])+g(d[s(d,q):],f[s(f,q):])
 return b
q="?"

Try it online!


Python 3, 549 530 509 453 449 410 406 394 393 391 bytes

I'm sure this can be improved, but it's a start:

def f(e,z,q="?",u=str.isnumeric):
 if e+z in(e,z):return""
 o,O,t,T,*x=e[0],e[1:2],z[0],z[1:2],e[1:],z[1:]
 if"?"in o+t:return f([e,x[0]][o==q],z)
 if u(o):
  if u(t):return t+f(*x)if O==q!=T else o+f(*x)if o==t or T==q!=O else 1
  return o+f(*x)
 if u(t):return t+f(*x)
def g(s):
 for a,b in zip(map(chr,range(65,91)),"2223334445556667777888999"):s=s.replace(a,b)
 return f(*s.split(" / "))

I'm using str.translate for the letters, and a wrapper function g to make the inputs in the format in which I want them. The actual function f is recursive, and will fail for ambiguous inputs. I still have lots of repititions in there though, so I'm sure there's lots of room for improvement.

Improvements:

  • saved 19 bytes by combining conditions
  • saved 21 bytes with ternaries
  • saved 56 bytes using a dictionary comprehension instead of the manual dictionary, thanks to @TuukkaX
  • saved 4 bytes by switching to the method suggested by @ovs, with @TuukkaX's improvement
  • saved 38 bytes with improvements from @ovs (and the last removable whitespace removed)
  • saved 4 bytes by putting defining str.isnumeric in a keyword argument
  • saved 12 bytes with combined comparison operators (e.g. T==q!=O)
  • saved 1 byte by turning not(e or z) into e+z in(e,z).
  • saved 2 bytes by saving the frequently used (E,Z)

Jelly, 84 bytes

+4 bytes - I think it should probably behave the same in all cases, so I've converted the keypad lookup integers back to digit-characters using +49Ọ.

”?e‘ḣ@µ”_eḤ‘ẋ@
;Ṃµ68DṬ+3RØAṁẇ@€FT+49Ọȯµ€Fṡ2i”?Ḃ$ÐḟÇ€
ḟ⁶ṣ”/Ç€ZLÐṂ€Q€LỊ$ÐfF€Ḣ€ḟ”_µL⁼⁵ȧ

A function which takes the string in the specified format and returns the phone number as a list of character or zero if invalid. As a program this is printed as if it were a string.

The way it works they could repeat the number more times
(e.g. "123456789_ / 123456789_ / 1234567890")
...or even only say it once, and the logic defined will apply.

Try it online!, or see all the sample inputs.

How?

”?e‘ḣ@µ”_eḤ‘ẋ@ - Link 1, helper to vary the length of a 2-slice: list s
”?             - literal '?'
  e            - exists in s                   (1 or 0)
   ‘           - increment                     (2 or 1)
    ḣ@         - head with reversed @rguments  (s or s[:1] - removes 2nd value if not '?')
      µ        - monadic chain separation, call that t
       ”_      - literal '_'
         e     - exists in t                   (1 or 0)
          Ḥ    - double                        (2 or 0)
           ‘   - increment                     (3 or 1)
            ẋ@ - repeat t that many times      (t*3 or t - [`_`]->['_','_','_'])

;Ṃµ68DṬ+3RØAṁẇ@€FT+49Ọȯµ€Fṡ2i”?Ḃ$ÐḟÇ€ - Link 2, reformat a phone number: char list of [0-9][A-Z], p
;                                     - concatenate p with
 Ṃ                                    - minimum of p - (?<_<0<1<...<9<A<...<Z - never "?" however, since it only follows a digit.)
                                      -   - this is simply to make a 2-slice with the last character on the left, as used at the very end of this link.
  µ                                   - monadic chain separation call that q
                       µ€             - monadic chain separation, for €ach v in q do:
   68                                 -   literal 68
     D                                -   cast to a decimal list -  [6,8]
      Ṭ                               -   untruth                -  [0,0,0,0,0,1,0,1]
       +3                             -   add 3                  -  [3,3,3,3,3,4,3,4]
         R                            -   range                  -  [[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3],[1,2,3,4],[1,2,3],[1,2,34]]
          ØA                          -   uppercase alphabet     -  ABCDEFGHIJKLMNOPQRSTUVWXYZ
            ṁ                         -   mould like the range ^ -  [ABC,DEF,GHI,JKL,MNO,PQRS,TUV,WXYZ]
             ẇ@€                      -   sublist v exists in that? for €ach, with reversed @rguments
                F                     -   flatten        (e.g. 'E' -> [0,1,0,0,0,0,0,0]; '4' -> [0,0,0,0,0,0,0,0]
                 T                    -   truthy indexes (e.g. 'E' -> [2]; '4' -> [])
                  +49                 - add 49
                     Ọ                - cast to character
                      ȯ               -   or             (e.g. 'E' -> [3]; '4' -> '4')
                         F           - flatten
                          ṡ2          - all slices of length 2
                                 Ðḟ   - filter discard if:
                                $     -   last two links as a monad:
                            i         -     first index of
                             ”?       -     literal '?'   (first index returns 0 if none exists)
                               Ḃ      -   mod 2 (so this filter discards pairs starting with '?')
                                   Ç€ - call the last link (1) as a monad for €ach slice

ḟ⁶ṣ”/Ç€ZLÐṂ€Q€LỊ$ÐfF€Ḣ€ḟ”_µL⁼⁵ȧ - Main link: string (or char list) s
ḟ                               - filter discard any:
 ⁶                              - literal ' '
  ṣ                             - split on:
   ”/                           - literal '/'
     Ç€                         - call the last link (2) as a monad for €ach
       Z                        - transpose
         ÐṂ€                    - filter, for €ach, keep items with minimal:
        L                       -   length
            Q€                  - de-duplicate €ach
                 Ðf             - filter keep items with:
                $               - last two links as a monad:
              L                 -   length
               Ị                -   insignificant? (=1 effectively here)
                   F€           - flatten €ach
                     Ḣ€         - head €ach
                       ḟ        - filter discard any of:
                        ”_      -   literal '_'
                          µ     - monadic chain separation, call that r
                           L    - length(r)
                             ⁵  - literal 10
                            ⁼   - equal?
                              ȧ - and r (0 if r did not result in a 10-digit list, else r)