Output integer given a digital clock style input

JavaScript (ES6),  105  104 bytes

Takes input as an array of 3 strings. Returns an array of digit characters.

a=>a.map(s=>s.match(/.../g).map(([a,b,c],n)=>'3789465021'[(o[n]=~~o[n]+[++a|7*++b^44*++c])%13]),o=[])[2]

Try it online!

How?

Finding a concise way to process the input

A common trick to turn an input string into an identifier is to use parseInt(). But it only works with alphanumeric strings and is therefore pointless here. Other than that, JS is not very good at processing ASCII codes in few bytes, so we'd better find another trick.

We can notice that each digit in this font can still be uniquely identified if we replace each character with a binary value: \$1\$ for space, \$0\$ for not space:

  0  |  1  |  2  |  3  |  4  |  5  |  6  |  7  |  8  |  9  
-----+-----+-----+-----+-----+-----+-----+-----+-----+-----
  _  |     |  _  |  _  |     |  _  |  _  |  _  |  _  |  _  
 | | |   | |  _| |  _| | |_| | |_  | |_  |   | | |_| | |_| 
 |_| |   | | |_  |  _| |   | |  _| | |_| |   | | |_| |  _| 
-----+-----+-----+-----+-----+-----+-----+-----+-----+-----
 101 | 111 | 101 | 101 | 111 | 101 | 101 | 101 | 101 | 101 
 010 | 110 | 100 | 100 | 000 | 001 | 001 | 110 | 000 | 000 
 000 | 110 | 001 | 100 | 110 | 100 | 000 | 110 | 000 | 100 

That looks like a good golfing start: it means that we can abuse the fact that a space is coerced to \$0\$ and can be turned into a \$1\$ by applying the pre-increment operator to it, while it will result in NaN for the other characters.

Hence the idea to implement something along these lines:

s.match(/.../g)        // split each line into groups of 3 characters
.map(([a, b, c], n) => // for each group (a, b, c) of characters at position n:
  ???                  //   do some bitwise magic with ++a, ++b and ++c
                       //   and use it to update an identifier for the n-th digit
)                      // end of map()

Bitwise magic

After some experiments and brute-forcing, it turned out that a pretty short and efficient formula is:

o[n] = ~~o[n] + [++a | 7 * ++b ^ 44 * ++c]

Step-by-step example:

  • The 1st line of a "0" is " _ ". Therefore, we have ++a -> 1, ++b -> NaN and ++c -> 1.

    So, ++a | 7 * ++b ^ 44 * ++c evaluates to 1 | 7 * NaN ^ 44 * 1, which is \$45\$.

    Because o[n] is initially undefined, ~~o[n] evaluates to \$0\$. And because \$45\$ is coerced to a string, o[n] is updated to "045".

  • The 2nd line of a "0" is "| |". This time, we have ++a -> NaN, ++b -> 1 and ++c -> NaN. The bitwise expression evaluates to \$7\$ and o[n] is updated to "457". (Note that the leading 0 is thrown away by ~~o[n].)

  • The 3rd line of a "0" is "|_|", leading to ++a -> NaN, ++b -> NaN and ++c -> NaN. The bitwise expression evaluates to \$0\$ and the final value of o[n] is "4570".

Minimal perfect hash function

Of course, the parameters of the bitwise formula were not chosen at random (well ... at least no completely): by coercing the final identifier of a digit back to an integer and applying a modulo \$13\$, we get a unique value in \$[0..9]\$.

The following table summarizes the results for all digits:

  digit |   0   |   1   |   2   |   3   |   4   |   5   |   6   |   7   |   8   |   9
--------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------
     ID |  4570 |  4377 | 45144 |  4511 |  4307 | 45441 | 45440 |  4577 |  4500 |  4501
--------+-------+-------+-------+-------+-------+-------+-------+-------+-------+-------
 mod 13 |   7   |   9   |   8   |   0   |   4   |   6   |   5   |   1   |   2   |   3

Hence the 10-byte lookup table: '3789465021'.


J, 55 bytes

(0 3,:3 3)(6 7 8 11 4 2 13 14 5 9 i.15|[:#.@,' '&=);.3]

Try it online!

J has a primitive ;.3 called Subarrays that let's you process a multi-dimensional "sliding window". Here we have a 3x3 window moving 3 steps to the right each time, which grabs exactly 1 digit.

We convert each 3x3 matrix to a boolean mask ' '&=, flatten it ,, and convert that binary number to decimal #.. After a little experimenting, I found that modding it by 15 15| returned a unique list of small numbers, and we just find the index within that.

More brute forcing could likely find an even more efficient encoding.


05AB1E, 25 bytes

€S3δôøðQε˜JC15%•#/ι®ˆ¼•sè

Input as a list of lines, output as a list of digits. If we could take the input as a 2D list of characters, the first 2 bytes can be removed.

Try it online.

Explanation:

€S                        # Convert each line in the (implicit) input-list of strings to
                          # an inner list of characters
   δ                      # Convert each inner list of characters to:
  3 ô                     #  Split it into blocks of size 3
     ø                    # Zip/transpose; swapping rows/columns
      ðQ                  # Check which characters are spaces (1 if truthy; 0 if falsey)
        ε                 # Map each 3x3 block to:
         ˜                #  Flatten it to a single list
          J               #  Join it together to a single string
           C              #  Convert it from binary to an integer
            15%           #  Take modulo-15
               •#/ι®ˆ¼•   #  Push compressed integer 105048012903067
                       s  #  Swap to take the earlier integer
                        è #  And (0-based) index it into this to get the resulting digit
                          # (after the map, the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers?) to understand why •#/ι®ˆ¼• is 105048012903067 (NOTE: the leading 1 and all the 0s except for the 3rd (7th digit) are fillers.