# Display words using 7-segment display

My first ever Programming Puzzles & Code Golf is the Seven-Slash Display. Here is my first Challenge, also based on 7-segment display.

I often think about, besides numbers, what kind of letters I can display using a simple 7 segment display. Turns out a lot of letters can be displayed. In fact, all but the letters K, M, V, W, X can be displayed using a single 7-segment display. This is because you can display either lower case or the upper case of a letter. e.g.

"abcdef" can be displayed as

 _     _     _  _
!_!!_ !   _!!_ !_
! !!_!!_ !_!!_ !


Note that each character is a 3x3 matrix made up of ! and _.

Of course, the 7-segment display can be used to display numbers and symbols:

 _     _  _  _           _  _     _
! _ !_ !_ !      _!  !!_ !_!!  !_!!_!
!    _!!_ !_!   !_!  ! _!!  !_ ! ! _!


Some letters can have both upper and lower case:

 _                 _
!   _ !_!!_   !   ! ! _ ! !
!_ !_ ! !! !  !  !!_!!_!!_!!_!


Here are the full set of characters:

 _     _  _     _  _  _  _  _                       _
! !  ! _! _!!_!!_ !_   !!_!!_!    _ !_     _     _  _!
!_!  !!_  _!  ! _!!_!  !!_! _!   !_ ! !  !!_!!_!   !

_     _     _  _  _                 _  _  _     _           _
!_!!_ !   _!!_ !_ !  !_!  !  !!   _ ! !!_!!_! _ !_ !_ ! !!_! _!
! !!_!!_ !_!!_ !  !_!! !  !!_!!_ ! !!_!!    !!   _!!_ !_! _!!_


Pay attention that there are a white space(), a dash(-) and a question mark(?). The letter I, O and Z are the same as numbers 1, 0 and 2 respectively.

In this challenge, you will write a program or a function to display a string using 7-segment display format above.

# Rules

1. You can write a program or a function

2. This is code-golf, the shortest code in bytes wins

3. Your program or function should take input from STDIN or as a parameter. And outputs a string to STDOUT or as a string in 3 lines without leading space but terminated with newline. Handle upper/lower case of CHIOU properly.

4. You may optionally print trailing white spaces

5. You must follow the format above. Using underscore _ and exclamation mark ! to form your 7-segment display.

6. You must support white space(), dash(-) and question mark (?)

7. If the string contain unsupported character (k, m, v, w, x), a single error character (3 horizon line, see example) is displayed. Besides the 5 unsupported characters, you can assume the input is made up of only supported character set.

8. I opted to not have a letter for lowercase L (l) due to confusions but if you are so inclined, you can display it like a 1 either to the right or left.

# Examples

$./a.out Start _ _ !_ !_ !_! _ !_ _!!_ ! !! !_$./a.out "7-seg dIsplay"
_     _  _  _           _  _     _
! _ !_ !_ !      _!  !!_ !_!!  !_!!_!
!    _!!_ !_!   !_!  ! _!!  !_ ! ! _!
$./a.out "0123456789 chiou-?" _ _ _ _ _ _ _ _ _ ! ! ! _! _!!_!!_ !_ !!_!!_! _ !_ _ _ _! !_! !!_ _! ! _!!_! !!_! _! !_ ! ! !!_!!_! !$./a.out "ABCDEFGHIJLNOPQRSTUZ"
_     _     _  _  _                 _  _  _     _        _
!_!!_ !   _!!_ !_ !  !_!  !  !!   _ ! !!_!!_! _ !_ !_ ! ! _!
! !!_!!_ !_!!_ !  !_!! !  !!_!!_ ! !!_!!    !!   _!!_ !_!!_
$./a.out "abcdefghijlnopqrstuz" _ _ _ _ _ _ _ _ !_!!_ _ _!!_ !_ ! !_ !! _ _ !_!!_! _ !_ !_ _! ! !!_!!_ !_!!_ ! !_!! ! !!_!!_ ! !!_!! !! _!!_ !_!!_$./a.out "Bad Form"
_
_
_
$./a.out "Hello" _ !_!!_ ! ! _ ! !!_ !_ !_ !_!$./a.out "World"
_
_
_

• Yes, you can assume there is no unsupported symbols. My intention is to make sure strings containing k, m, v, w, x do not get displayed. – some user Jul 29 '15 at 6:29
• – DavidC Jul 29 '15 at 7:45
• I think you confused carriage return (CR, \r) with line feed (LF, \n). *nix uses LF and Windows uses CRLF. Only certain legacy systems use CR by itself. More information here: en.wikipedia.org/wiki/Newline – Winny Jul 29 '15 at 8:36
• Your "8" character seems to have a bar "|" instead of an exclamation mark "!". Is it intended? – coredump Jul 29 '15 at 13:20
• @Winny, you are right of course. I was too lazy to look it up when I wrote it. – some user Jul 29 '15 at 15:04

# CJam, 123114112 110 bytes

Qq_eu";=KMVWX":T&"@"@?" -chiou"Tereu{i"^A^W^G;^Þ¯     ^Þ^Û9³·^É¿»
^@
^P
^Ü^Ò½7¦^_¶´§=   ^O^V&5^U¯¼¹^T³6/"=i2b9Ue[3/"!_!"f.{S?}.+}/N*


The above uses caret notation, since the code contains unprintable characters. One of them is a null byte (^@), which means that this code can only be executed from the command line.

At the cost of only two more bytes (for a total of 112), we can fix this.

Qq_eu";=KMVWX":T&"@"@?" -chiou"Tereu{i"AWG{ÞïIÞÛyó÷Éÿû
@
P
ÜÒýwæ_öôç}IOVfuUïüùTóvo"=i448+2b1>3/"!_!"f.{S?}.+}/N*


This time, all characters are printable. Try it online in the CJam interpreter.

### Example run

$LANG=en_US$ xxd -ps -r > 7seg.cjam <<< 51715f6575223b3d4b4d565758223a5426224022403f22202d6368696f752254657265757b69220117073b9eaf099e9b39b3b789bfbb0a000a100a9c92bd37a61fb6b4a73d090f16263515afbcb914b3362f223d6932623955655b332f22215f2122662e7b533f7d2e2b7d2f4e2a
$wc -c 7seg.cjam 110 7seg.cjam$ echo -n '0123456789 chiou-?' | cjam 7seg.cjam; echo
_     _  _     _  _  _  _  _                       _
! !  ! _! _!!_!!_ !_   !!_!!_!    _ !_     _     _  _!
!_!  !!_  _!  ! _!!_!  !!_! _!   !_ ! !  !!_!!_!   !
$echo -n 'ABCDEFGHIJLNOPQRSTUYZ' | cjam 7seg.cjam; echo _ _ _ _ _ _ _ _ _ _ !_!!_ ! _!!_ !_ ! !_! ! !! _ ! !!_!!_! _ !_ !_ ! !!_! _! ! !!_!!_ !_!!_ ! !_!! ! !!_!!_ ! !!_!! !! _!!_ !_! _!!_$ echo -n 'World' | cjam 7seg.cjam; echo
_
_
_


### Idea (printable version)

Each character can be shown on the 9-segment display

!_!
!_!
!_!


by replacing some of its characters characters with spaces.

We can turn a specific character into an integer by replacing each shown segment, in natural reading order, with a 1, each not shown segment with a 0 and considering the result binary digits.

The first and third segment are never shown, so this will produce integers in the ranges [0,64) and [128,192).

We can encode each of these integers as a single byte, but half of them will result in unprintable characters. Thus, we add 64 to each integer before casting to character, which makes sure the code points are in the ranges [64,128) and [192,256).

The only unprintable character in these two ranges is DEL (code point 127), which corresponds to the following, unsued display configuration:

!_!
!_!


We can reverse the above encoding by adding 448 == 512 - 64 to each code point, converting to base 2 and removing the first binary digit.

All that's left to to find an efficient way of associating these encoded segmenets with their corresponding ASCII characters.

If we map the characters of " -chiou" to the characters of ";=KMVWX" and convert the entire input to uppercase, we can simply store encoding for all characters between 0 (code point 48) and Z (code point 90), giving a range of 43.

Array indexing is modular in CJam, so if A is a string of length 43, A86=, A43= and A0= all yield the same results. The character with code point 86 is V, so we simply store the encoded segments of V - Z and 0 - U, in order.

In the actual code, we select the at sign as "bad form" character, replace the entire input with the string "@" if it contains a forbidden letter and reverse the steps from above.

### Code (printable version)

Q            e# Push an empty array for posterior concatenation.
_eu          e# Copy the input and convert is to uppercase.
";=KMVWX":T& e# Intersect the result with with T := ";=KMVWX".
"@"@?        e# Select "@" if truthy and the input if falsy.
" -chiou"Ter e# Perform transliteration.
eu           e# Convert everything to uppercase.
{            e# For each character in the modified input:
i          e#   Push its code point.
"…"=       e#   Select the corresponding character from the string.
i448+      e#   Push that character's code point and add 448.
2b1>       e#   Convert to base 2 and discard the first digit.
3/         e#   Group into triplets of digits (rows).
"!_!"      e#   Push that string.
f.{        e#   For each group of digits:
S?       e#     Select the corresponding char of "!_!" for 1 and " " for 0.
}          e#
.+         e#   Concatenate along the rows with the previous results.
}/           e#
N*           e# Join, separating by linefeeds.


# Perl, 475469424390280 272 bytes

$_=<>;die" - "x3if/[kmvwx]/i;y/chiou/kmvwx/;$_=lc;while(/(.)/g){$z=ord($1=~y/a-z0-9\-\? /{v\x17nWS7z(.F\x16rb?[yBuV> f&|O?(Omxuw)\x7f}@K\0/r);$i.=$z&1?' _ ':$"x3;$m.=($z&16?'!':$").($z&64?'_':$").($z&8?'!':$");$p.=substr(" ! _!_",$z&6,2).($z&32?'!':$")}print"$i$m
$p "  multi-line with comments: $_=<>;   # accept input
die" -   # check for invalid chars
"x3if/[kmvwx]/i;
y/chiou/kmvwx/;$_=lc; # substitute invalid chars, convert to lowercase while(/(.)/g) # loop over each character # lookup character from string using transliteration and convert to # number using ord() for bit checking: {$z=ord($1=~y/a-z0-9\-\? /{v\x17nWS7z(.F\x16rb?[yBuV> f&|O?(Omxuw)\x7f}@K\0/r);$i.=$z&1?' _ ':$"x3;    # first row
$m.=($z&16?'!':$").($z&64?'_':$").($z&8?'!':$"); # second row$p.=substr("  !  _!_",$z&6,2).($z&32?'!':$")} # third row # print result: print"$i
$m$p
"


The bit patterns encoding the segments are stored in a string (escaping 3 unprintable chars using \x and using \0 for space) and are mapped to the input characters using the Perl transliteration operator.

For 5 of the 7 segments, a bitwise and is used along with the ternary operator to output a space or the segment character. For the bottom left two segments (encoded by 2 and 4 in the bitset), a substring lookup into an 8 character string is used to save 2 bytes.

Thanks to Dom Hastings for his Perl golfing tips.

Old version (using regexes to encode the patterns), 390 bytes:

$_=<>;if(/[kmvwx]/i){print" -\n"x3;exit}y/chiou/kmvwx/;$_=lc;while(/(.)/g){$z=$1;$i.=($z=~/[acefgopqsz\?0235-9]/?' _ ':'   ');$m.=($z=~/[abce-hlmopqstuy045689]/?'!':' ').($z=~/[abdefhkmnp-twyz\-\?2-689]/?'_':' ').($z=~/[adhijopquyz\?0-4789]/?'!':' ');$p.=($z=~/[a-hj-prtuwxz\?0268]/?'!':' ').($z=~/[b-egjklostuw-z0235689]/?'_':' ').($z=~/[abdg-jmnoqsu-y013-9]/?'!':' ')}print"$i\n$m\n$p\n"  multi-line with comments: $_=<>;   # accept input
if(/[kmvwx]/i){print" -\n"x3;exit}   # check for invalid chars
y/chiou/kmvwx/;$_=lc; # substitute invalid chars, convert to lowercase while(/(.)/g) {$z=$1;$i.=($z=~/[acefgopqsz\?0235-9]/?' _ ':' '); # first row$m.=($z=~/[abce-hlmopqstuy045689]/?'!':' '). ($z=~/[abdefhkmnp-twyz\-\?2-689]/?'_':' ').
($z=~/[adhijopquyz\?0-4789]/?'!':' '); # second row$p.=($z=~/[a-hj-prtuwxz\?0268]/?'!':' '). ($z=~/[b-egjklostuw-z0235689]/?'_':' ').
($z=~/[abdg-jmnoqsu-y013-9]/?'!':' ')} # third row print"$i\n$m\n$p\n" # print result


The string is read in and checked for invalid chars using a regex, exiting if any are found. Then the allowed lowercase chars are substituted for the invalid chars and the entire string is converted to lowercase.

The rows are generated one at a time, with 1 segment per letter on the first row and 3 on the other two. For each row, the string is processed one character at a time and the character is matched against a regex for each segment to check if a ! or _ should be displayed. Using a regex means that for characters where the segment is not set it takes zero bits per segment per character to encode whether to set it, and for those where it is it takes a little less than 8 bits on average because regex character ranges can be used. So it works out to around 3 or 4 bits per segment per character in the set, or around 21-24 bits per character.

It doesn't handle line-wrapping.

• Hey @samgak, glad to see more Perl love! I noticed a few things that could help you shave off some bytes and wanted to share them! You can use some of Perl's magic variables to help reduce in this; ' ' can be replaced with $" and ' ' can be $"x3 which trims a few off, your \ns can be literal newlines to get rid of a few more. Your early exit can be shorted too, using die, so that if(/[kmvwx]/i){print" -\n"x3;exit} becomes die" - "x3if(/[kmvwx]/i). With a bit more fudging as well you can re-arrange the loop to avoid the brackets and you shouldn't need $z to save a few more! – Dom Hastings Jul 30 '15 at 11:28 • I hope you aren't offended, but I had a look at reducing more, gist.github.com/dom111/e651b5de8c7e7fc9a6cf. Down to 323! – Dom Hastings Jul 30 '15 at 11:33 • @DomHastings no offence taken at all, thanks for the great golfing tips! I've used as many of them as I could in my edited answer, which I've changed to use bitwise encoding instead of regexes for the segment patterns. Unfortunately$z is back, and I can't figure out how to get rid of it. Also, $_=lc<> doesn't work, because then the code can't discriminate between upper and lowercase CHIOU – samgak Jul 31 '15 at 2:36 # Common Lisp, 488 416 (lambda(z)(dotimes(r 3)(map()(lambda(c)(do((i 0(+ 17 i))n)((or(and(> i 901)(setf n 146))(=(char-code c)(ldb(byte 8 9)(setf n(ldb(byte 17 i)#36RIL884OIVFXJY4DCQ0O8DPH8MOMR2DSLPP3O4ESYHS234A9HEQYSV8IBDBZI6Z3C3MCVR77OYD3QN5G6CX2UQWGL4UY5R9PKYI1JQ5Y6DC27MQQGUZSCGI8Q9JCYP9N1L4YYKRWM1ZNMSVTSB4792UUWV6Z3906VSP981WCCBMDNJ02)))))(loop for v from(* 3 r)for d across"!_!"do(format t"~:[ ~;~A~]"(logbitp v n)d)))))z)(terpri)))  ### Example With "abcdefg'hijklnopqrstuz", prints:  _ _ _ _ _ _ _ _ _ _ !_!!_ _ _!!_ !_ ! _ !_ ! ! _ ! _ _ !_!!_! _ !_ !_ _! ! !!_!!_ !_!!_ ! !_! _ ! ! !!_! _ !_ ! !!_!! !! _!!_ !_!!_  ### Remarks Characters and their representations are encoded in this number in base 36: IL884OIVFXJY4DCQ0O8DPH8MOMR2DSLPP3O4ESYHS234A9HEQYSV8IBDBZI6Z3C3MCVR77OYD3QN5G6CX2UQWGL4UY5R9PKYI1JQ5Y6DC27MQQGUZSCGI8Q9JCYP9N1L4YYKRWM1ZNMSVTSB4792UUWV6Z3906VSP981WCCBMDNJ02  The binary representation of this digit is divided in groups of 17 bits. For example, the last group of 17 bits is 110000111101010, which is decomposed here in two parts: 1. 110000, the char-code of character 0 2. 111101010, an encoding of the drawing, best represented as follows: 010 (bits 0-2) _ 101 (bits 3-5) => ! ! 111 (bits 6-8) !_!  Bits in first and last "column" are for ! characters, the ones in the middle column for the _ character. When necessary, both uppercase and downcase versions of a character are stored. The function iterates three times over the input string, one for each line of output, searches for a matching character in the table (or defaults to 146, a.k.a. three bars), and prints the representation at current row. • Thank you for participating in the challenge. I noticed that your 'K' is displayed as 'L' in your output. You may have misunderstood the requirement that unsupported character cannot be printed along with other valid character. Also, I see that you added the ' character, which is nice; however, it will be displayed outside of what a 7-segment display capable of. If you move the ! down by 1 line, it would be perfect. – some user Jul 30 '15 at 4:00 • I'll update the ' character and edit the question for K because in fact, I put the K in the wrong place in the input string ("...jlKn...") ;-) You can see the triple bars (error) just after the L. Thanks for noticing it. – coredump Jul 30 '15 at 7:49 # JavaScript (ES6), 380352 324 bytes (Note: Code uses caret notation, as it contains some unprintable characters. To get original code, click here and select the raw data. And no, h is not a CJam program. ;) d=s=>{a=' ',u=n=>r>>n&1?'!':a,v=n=>r>>n&1?'_':a,g='ABCDEFGHIJLNOPQRSTUYZabcdefghijlnopqrstuyz0123456789-? ÿ',h='{vUnWSuz(lTb}[;B7V|>O{vFnWSur lTbf[;B7Vd>O}(O/:7w)^??^BK^0^G',x=y=z='';for(i=0;i<s.length;x+=a+v(0)+a,y+=u(4)+v(1)+u(3),z+=u(6)+v(2)+u(5)){q=g.indexOf(s[i++]);if(q<0)return dÿ;r=h.charCodeAt(q)}return x+${y}
+z}


Called as d("7-seg display") or similar. Works in Firefox 40, but may not in other browsers. For some reason, the HTML/JS snippet does not save the non-printables, but you can copy-paste the raw data from over here.

# Ungolfed:

(Note: g and h have been padded with spaces to match 8, -, ÿ and space with their corresponding Unicode values.)

d = function (s) {
t = function (n) { return Math.floor(n) % 2; };
g = 'ABCDEFGHIJLNOPQRSTUYZabcdefghijlnopqrstuyz012345679? 8      -      ÿ      space ';
h = '{vUnWSuz(lTb}[;B7V|>O{vFnWSur lTbf[;B7Vd>O}(O/:7w)?K \u007f \u0002 \u0007 \u0000';
x = y = z = '';
for(var i = 0; i < s.length; i++) {
q = g.indexOf(s.charAt(i));
if (q < 0)          // if g does not contain the character
return d('ÿ');    // ÿ is equivalent to the error character, '\u0007'
r = h.charCodeAt(q);
x += ' ' + (r % 2 === 1 ? '_' : ' ') + ' ';
y += (t(r / 16) === 1 ? '!' : ' ') + (t(r / 2) === 1 ? '_' : ' ') + (t(r / 8) === 1 ? '!' : ' ');
z += (t(r / 64) === 1 ? '!' : ' ') + (t(r / 4) === 1 ? '_' : ' ') + (t(r / 32) === 1 ? '!' : ' ');
}
return x + '\n' + y + '\n' + z;
}


# Explanation:

I immediately noticed that the 7 segments, converted into 0/1 bits, would go well with the first 128 Unicode characters. The problem with this idea is that 1/4 of these characters are non-printable control characters. Using them in my code would make it look look incredibly messy (or incredibly intelligent; I haven't decided which one). To solve this while keeping the rest of the code simple, I came up with this idea:

With the exception of -, space, and error, none of the characters were missing both of the lower vertical segments. So to make sure that all those characters stayed between 0020 and 007f, I simply mapped the 64 and 32 bits to these segments, like so:

     1
---
16 |   | 8
2 ---
64 |   | 32
---
4


The other 5 segments' numbers aren't too important; they could be arranged in any other way and still have all the same characters "in-bounds".

As an example, here's the encoded version of A:

     1
---
16 |   | 8
2 ---
64 |   | 32

Dec: 64 + 32 + 16 + 8 + 2 + 1 = 123
Hex: 40 + 20 + 10 + 8 + 2 + 1 = 7B = u+007B = {


I then stuffed the encoded version of each 7-seg character in h. However, 8 resulted in 007f (the delete control code; constant no matter how the segments are arranged), space resulted in 0000 (the null code; also constant), - resulted in 0002, and error resulted in 0007. I copy-pasted the raw bytes into the correct position for 8, -, and error; space was easily achieved with \0.

After all this encoding, all I had to do was use it to decode the string and output it in a 7-seg readable format. I used a for loop and three variables (x, y, and z, each corresponding to an output line) to go through each character in the string and add its 7-seg equivalent to the output. I chose ÿ for the error character because AFAIK, it's not on any keyboard, and it's the last character in the u+0000-u+00ff range. Perhaps I could have been witty and chosen Ξ (Greek letter xi) instead.... ;)

Edit 1: Saved a bunch of space by creating mini-functions to determine whether !, _, or is needed.

Edit 2: Saved a bunch more space using the tricks I've learned since I last visited this post.

As usual, suggestions are greatly appreciated!