Read a retro display

22

2

Art stolen from What size is the digit?


7-segment digits can be represented in ASCII using _| characters. Here are the digits 0-9:

 _     _  _       _   _  _   _   _ 
| | |  _| _| |_| |_  |_   | |_| |_|
|_| | |_  _|   |  _| |_|  | |_|  _|

Your job is to parse the art into normal numbers.

Notes on numbers

  • Each digit has a different width.
    • 1 has a width of 1
    • 3 and 7 are 2 wide
    • 245689 and 0 are all 3 wide

Also between each digit is one char of padding. Here is the full char set:

 // <-- should be one space, but SE formatting messed it up
|
|
-------------
 _ 
 _|
|_ 
-------------
_ 
_|
_|
-------------

|_|
  |
-------------
 _ 
|_ 
 _|
-------------
 _ 
|_ 
|_|
-------------
_ 
 |
 |
-------------
 _ 
|_|
|_|
-------------
 _ 
|_|
 _|
-------------
 _ 
| |
|_|

Input

Input can be either from the console or as a string arg to a function.

Output

Output is either put to the console or is returned from the function.

Examples:

  _  _   _ 
|  |  | |_ 
|  |  | |_|
1776

 _   _     _ 
 _| | | | |_ 
|_  |_| | |_|
2016

   _       _ 
| |_| |_| |_ 
|  _|   |  _|
1945

   _   _   _   _   _   _ 
| | | | | | | | | | | | |
| |_| |_| |_| |_| |_| |_|
1000000

 _     _  _       _   _  _   _   _ 
| | |  _| _| |_| |_  |_   | |_| |_|
|_| | |_  _|   |  _| |_|  | |_|  _|
0123456789

This is code-golf so shortest byte count wins!

J Atkin

Posted 2016-02-13T20:25:00.580

Reputation: 4 846

Related – J Atkin – 2016-02-13T20:48:14.647

I am interested in learning about the best algorithms to solve this kind of problem, and I'm having difficulty learning from the answers here (they're good, just very concise). Is there a place you can refer me to see more lengthy explanations, preferably with pictures? – None – 2016-02-17T01:51:00.377

Well, The way mine works is fairly simple. It transposes the list and loops over it. It then splits on empty lines. Each number is checked against a fingerprint table for each number. The others work a bit like mine, except instead of a fingerprint table they basically have a hash table they use.

– J Atkin – 2016-02-17T01:57:33.997

Is there a more general name for this type of problem in computer science? – None – 2016-02-17T02:10:09.500

I have no idea ;) – J Atkin – 2016-02-17T02:57:31.213

Answers

4

Pyth, 33 30 bytes

sm@."/9Àøw"%%Csd409hTcC.z*3d

Here's the idea: Once we transpose the input, and split into digits, we can sort of hash the individual digit strings and assign them to their values.

sm@."/9Àøw"%%Csd409hTcC.z*3d     Implicit: z=input
                      C.z        Transpose input.
                     c   *3d     Split that on "   ", a space between digits.
 m@."/9Àøw"%%Csd409hT            Map the following lambda d over that. d is a digit string.
             Csd                   Flatten the digit string, and convert from base 256.
            %   409                Modulo that by 409
           %       hT              and then by 11. All digits go to a distinct num mod 11.
   ."/9Àøw"                        The compressed string "03924785/61".
  @                                Index into that string.
s                                Flatten and implicitly output.

Try it here.

lirtosiast

Posted 2016-02-13T20:25:00.580

Reputation: 20 331

Cool, basically looks like my approach but far shorter! – Lynn – 2016-02-15T20:08:14.353

@Lynn Sorry about that; there's basically one shortest way to do this in Pyth. – lirtosiast – 2016-02-15T20:09:06.897

4

Ruby, 184 bytes

a=0
l=$<.map{|x|x.bytes.map{|y|y==32?0:1}+[0]*2}
(0..l[0].count-1).map{|i|l[0][i]+2*l[1][i]+4*l[2][i]}.each{|x|
x>0?(a=x+2*a):(p Hash[[40,6,32,20,18,26,42,8,44,64].zip(0..9)][a];a=0)}

Explanation

  • takes the input from stdin
  • converts the strings to binary sequences, 1/0 for segment on/off
  • encodes columns to 3bit binary number
  • encodes sequences of 3 bit numbers to 9 bit numbers, use '0' columns as stop symbols
  • use a lookup table to convert the 9 bit numbers to digits

This is my first code-golf. Thanks for the fun!

bogl

Posted 2016-02-13T20:25:00.580

Reputation: 151

2Welcome to PPCG! Very nice job on your first post! – J Atkin – 2016-02-15T18:11:58.930

2

Japt, 119 bytes

Ur"[|_]"1 z r" +
"R x1 qR² £"11
1 1
1151151
111
 15111
115 1
 1
115 1
111
1511
111
15  1
11511
111
115 1
111
11"q5 bXÃq

Try it here!

Oh geez, this one's really long. I don't think I have finished golfing.

Explanation

Preparation

We take the input and convert any |_ to 1. Then we transpose, strip out ending spaces, and split along double-newlines.

Translation

We map over the resulting array and find the index where the form appears in a reference array. Here's a diagram to help:

MAPITEM
  11
  1 1 --> This same form appears at index 0 in the reference array
  11                            |
                                |
                                V
                        change the mapitem to 0!

After that, we join the array of numbers and output!

NOTE: You may be wondering why we have to change each art character to a series of 1's. This is because there seems to be a bug (or something like that) which doesn't let me store the characters as is with |_.

Mama Fun Roll

Posted 2016-02-13T20:25:00.580

Reputation: 7 234

I noticed the _ bug, but I don't know what caused it. – ETHproductions – 2016-02-14T23:47:31.157

OK, "\n\n" can be replaced with , and "\\||_" with "%||_". I think you could also save some bytes by encoding the long string in base 4 (changing each of the 4 disinctive chars to 0, 1, 2, or 3, padding to a length of a multiple of 4, then running r"...."_n4 d} over it), but for some reason, I haven't gotten this to work yet. – ETHproductions – 2016-02-15T20:18:28.247

2

Python2, 299 261 244 bytes

s=lambda a,i=0:[a]if i==len(a[0])else[[j[:i]for j in a]]+s([j[i+1:]for j in a])if all(j[i]==' 'for j in a)else s(a,i=i+1)
p=lambda l:['95572431508448853268'.find(`sum(ord(c)**i for i,c in enumerate("".join(n)))%108`)/2for n in s(l.split('\n'))]

I really liked this challenge, good job !

Explanation

The function s takes the three lines as input, it tries to find a digit separation (all characters are spaces). When such a separation is found it calls s with the rest of the three lines and add the value returned by the call to the three lines that compose the digit. If there is no separation it means there is only one digit.

The function p is the entry point so it takes a string that represent the digits. The digits are stored as a "hash" computed with sum(ord(c)**i for i,c in enumerate("".join(n)))%108 to save space (thanks to other answers !).

Exemple

digits="""
 _     _ 
| | |  _|
|_| | |_ """[1:]  # remove the '\n' at the beginning

p(digits)  # [0, 1, 2]

Other versions

261 bytes (py3):

s=lambda a,i=0:[a]if i==len(a[0])else[[j[:i]for j in a]]+s([j[i+1:]for j in a])if all(j[i]==' 'for j in a)else s(a,i=i+1)
def p(l):[print([91,21,84,31,58,76,88,41,80,68].index(sum(ord(c)**i%20 for i,c in enumerate("".join(n)))),end="")for n in s(l.split('\n'))]

249 bytes, this one transpose the lines (py2):

f="".join
s=lambda a,i=0:[a]if i==len(a)else[a[:i]]+s(a[i+1:])if all(c==' 'for c in a[i])else s(a,i=i+1)
h=lambda s:ord(s[0])**len(s)+h(s[1:])if s else 0
p=lambda l:["10220907112527153129".index(`h(f(map(f,n)))%34`)/2for n in s(zip(*l.split('\n')))]

Dica

Posted 2016-02-13T20:25:00.580

Reputation: 409

2

Pyth, 39 bytes

sm@"413-928-506--7"%%Crd6C\524cjbC.z*3d

This seems to work? Try it online.

Lynn

Posted 2016-02-13T20:25:00.580

Reputation: 55 648

2

JavaScript (ES6), 169 bytes

a=>[...(a=a.split`
`)[0]].map((b,c)=>(d={' ':0,'|':1,'_':2})[b]+d[a[1][c]]*2+d[a[2][c]]).join``.split(0).map(b=>[343,3,182,83,243,281,381,23,383,283].indexOf(+b)).join``

Starts by splitting into three lines, remapping each column into a value, and then building a unique identity for each column from those values. It then splits by 0 (the identity for the space between columns), and finally maps each identity to it's number values, which it concatenates and outputs.

Mwr247

Posted 2016-02-13T20:25:00.580

Reputation: 3 494

Very nice! I wish python had a list split function... – J Atkin – 2016-02-16T17:06:36.933

@JAtkin I joined into a string so that I could split it. I believe you could do that in Python as well? – Mwr247 – 2016-02-16T17:20:53.870

0

Python 3, 281 254 bytes

Edit

I just looked at the code for the other python answer and noticed that much of the code is similar. This was arrived at independently.

(newlines added for "readability")

def p(i):
 n=[[]]
 for l in zip(*i.split('\n')):
  if all(i==" "for i in l):n+=[[]]
  else:n[-1]+=l
 return''.join(map(lambda l:str([''.join(l[2:])==x for x in
             "|_ _ ||,|,|___ | ,_ ||,  _  ||, ___  |,|___  |,  ||,|___ ||, ___ ||"
                     .split(',')].index(1)),n))

Ungolfed:

def parse(input):
    lines = list(input.split('\n'))
    numbers = [[]]
    for lst in zip(*lines):
        if all(i==" " for i in lst):
            numbers += [[]]
        else:
            numbers[-1] += lst
    return ''.join(map(digit, numbers))

def digit(num):
    fingerprint = 
        "|_ _ ||,|,|___ | ,_ ||,  _  ||, ___  |,|___  |,  ||,|___ ||, ___ ||".split(',')
    return str([''.join(num[2:]) == y for y in fingerprint].index(True))

Tests:

assert (parse("   _   _   _   _   _   _ \n| | | | | | | | | | | | |\n| |_| |_| |_| |_| |_| |_|") == '1000000')
assert (parse("   _       _ \n| |_| |_| |_ \n|  _|   |  _|") == '1945')
assert (parse(" _   _     _ \n _| | | | |_ \n|_  |_| | |_|") == '2016')
assert (parse(" _     _  _       _   _  _   _   _ \n| | |  _| _| |_| |_  |_   | |_| |_|\n|_| | |_  _|   |  _| |_|  | |_|  _|") == '0123456789')
assert (parse("  _  _   _ \n|  |  | |_ \n|  |  | |_|") == '1776')

How it works

(Note: I am explaining the ungolfed program here since it is more readable and it has the exact same code, with the exception that the digit function is in-lined into a lambda)

def parse(input):
    lines = list(input.split('\n'))
    numbers = [[]]

The main function is parse. It first splits the input into lines and creates the numbers array.

    for lst in zip(*lines):
        if all(i==" " for i in lst):
            numbers += [[]]
        else:
            numbers[-1] += lst

This is my favorite part (since it took so long to figure out). Here we zip the lines so we can basically vertically traverse the input. When the line has chars on it we add it to the last number in the numbers array. If it does not have any chars on it we add a new number to the array.

    return ''.join(map(digit, numbers))

Really simple, numbers is mapped with the digit function and is converted to a string.

def digit(num):
    fingerprint = 
        "|_ _ ||,|,|___ | ,_ ||,  _  ||, ___  |,|___  |,  ||,|___ ||, ___ ||".split(',')
    return str([''.join(x[2:]) == y for x, y in zip([num]*10, fingerprint)].index(True))

This is (fairly) simple. fingerprint is the string representation of the digits created above minus the first 2 chars (this was the smallest fingerprint I could find). We return the index of the first match.

J Atkin

Posted 2016-02-13T20:25:00.580

Reputation: 4 846

0

Haskell, 270 207 bytes

Don't be too hard, this is my first ever haskell program ;) I'm almost certain this can be golfed further, but I don't know how given my limited knowledge of the language.

import Data.Lists
b n=map c$splitOn["   "]$transpose$lines n
c n|e<-drop 2$concat n,Just r<-elemIndex True[e==f|f<-splitOn",""|_ _ ||,|,|___ | ,_ ||,  _  ||, ___  |,|___  |,  ||,|___ ||, ___ ||"]=(show r)!!0

Ungolfed:

module Main where
import Data.Lists

main :: IO ()
main = print $ parse " _     _  _       _   _  _   _   _ \n| | |  _| _| |_| |_  |_   | |_| |_|\n|_| | |_  _|   |  _| |_|  | |_|  _|"

parse :: String -> String
parse n = let lst = transpose $ lines n
              numbers = splitOn ["   "] lst --"   " lst
              number = map digit numbers
          in number

digit :: [String] -> Char
digit n | e <- drop 2 $ intercalate "" n
        , fingerprint <- ["|_ _ ||","|","|___ | ","_ ||","  _  ||"," ___  |","|___  |","  ||","|___ ||"," ___ ||"]
        , Just res <- elemIndex True [e == finger | finger <- fingerprint]
        = head $ show res

Big thanks to @nimi for the tips!

J Atkin

Posted 2016-02-13T20:25:00.580

Reputation: 4 846

bad news first: I'm afraid you have to include the import Data.List in your byte count. Good news: a) if you have Data.Lists installed you can import it instead and replace a with splitOn: ...map c$splitOn[" "]$transpose... and ...f<-splitOn",""|_.... b) intercalate "" n is concat n or id=<<n. c) replace res with a single letter name. d) use pattern guards instead of let ... in: c n|e<-drop 2$id=<<n,Just r<-elemIndex ... ]=(show r)!!0. – nimi – 2016-02-16T22:51:26.023

Hehehe, Whoops! The import was lost in the copy/paste ;) Thanks for all the tips! – J Atkin – 2016-02-16T23:01:55.577

@nimi Sorry to bother you about it, but do you mind explaining what =<< does? Neither the hoogle docs or the type signature are very helpful to me. – J Atkin – 2016-02-17T00:33:31.173

=<< in list context is concatMap, i.e. it maps the given function over the list and combines the results into a single list. >>= does the same but with the arguments flipped. id =<< n (or n >>= id) maps the identity function over the list (of lists), i.e. does nothing with the sublists and the concatenates them. So it's the same as concat. – nimi – 2016-02-17T06:30:39.530