Seven-Segment Devices
February 27, 2018
We ignore the restriction on 16-bit integers and write a program to output any number of digits. The output is a list of numbers, one for each digit. The correspondence of input digit to output number can be pre-computed:
0: 2^0 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 = 125 1: 2^4 + 2^6 = 80 2: 2^0 + 2^1 + 2^2 + 2^4 + 2^5 = 55 3: 2^0 + 2^1 + 2^2 + 2^4 + 2^6 = 87 4: 2^1 + 2^3 + 2^4 + 2^6 = 90 5: 2^0 + 2^1 + 2^2 + 2^3 + 2^6 = 79 6: 2^0 + 2^1 + 2^2 + 2^3 + 2^5 + 2^6 = 111 7: 2^2 + 2^4 + 2^6 = 84 8: 2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^5 + 2^6 = 127 9: 2^0 + 2^1 + 2^2 + 2^3 + 2^4 + 2^6 = 95
Of course, I wrote a program to make that computation:
(define (digit . xs)
(let loop ((xs xs) (sum 0))
(if (null? xs) sum
(loop (cdr xs) (+ sum (expt 2 (car xs)))))))
> (digit 0 2 3 4 5 6) ; 0 125 > (digit 4 6) ; 1 80 > (digit 0 1 2 4 5) ; 2 55 > (digit 0 1 2 4 6) ; 3 87 > (digit 1 3 4 6) ; 4 90 > (digit 0 1 2 3 6) ; 5 79 > (digit 0 1 2 3 5 6) ; 6 111 > (digit 2 4 6) ; 7 84 > (digit 0 1 2 3 4 5 6) ; 8 127 > (digit 0 1 2 3 4 6) ; 9 95
Then it’s easy to solve Bentley’s task:
(define (bentley n)
(let ((ds (vector 125 80 55 87 90 79 111 84 127 95)))
(map (lambda (d) (vector-ref ds d)) (digits n))))
> (bentley 3141592654) (87 80 90 80 79 95 55 111 79 90)
You can run the program at https://ideone.com/FqkeCR.
Simple perl one liner… first part converts the digits into a byte string {using tr}. The second just dumps it so it’s readable”
perl -e '($_=shift @ARGV)=~tr/0123456789/}P7WZOoT\x7f_/;print qq(@{[unpack q(c*)]}\n);' 3141592654;; Let's have some fun with the format specfiers! (defparameter *line-0* "~0@*~:[ ~; ----- ~] ") (defparameter *line-1* "~1@*~:[ ~; ----- ~] ") (defparameter *line-2* "~2@*~:[ ~; ----- ~] ") (defparameter *line-3* "~3@*~:[ ~;|~] ") (defparameter *line-4* "~4@*~:[ ~;|~] ") (defparameter *line-5* "~5@*~:[ ~;|~] ") (defparameter *line-6* "~6@*~:[ ~;|~] ") (defun format-digit-line (digit-count &rest segment-formats) (let ((*print-circle* nil)) (format nil "~~~A:{~{~A~}~~:}~~%" digit-count segment-formats))) (defconstant +digit-count+ 5) (defparameter *segment-display* (concatenate 'string "~@*" (format-digit-line +digit-count+ *line-2*) "~@*" (format-digit-line +digit-count+ *line-3* *line-4*) "~@*" (format-digit-line +digit-count+ *line-3* *line-4*) "~@*" (format-digit-line +digit-count+ *line-3* *line-4*) "~@*" (format-digit-line +digit-count+ *line-1*) "~@*" (format-digit-line +digit-count+ *line-5* *line-6*) "~@*" (format-digit-line +digit-count+ *line-5* *line-6*) "~@*" (format-digit-line +digit-count+ *line-5* *line-6*) "~@*" (format-digit-line +digit-count+ *line-0*))) (defun test/digits () (let ((digit-count 9)) (loop :for format-spec :across (vector (format-digit-line digit-count *line-2*) (format-digit-line digit-count *line-3* *line-4*) (format-digit-line digit-count *line-1*) (format-digit-line digit-count *line-5* *line-6*) (format-digit-line digit-count *line-0*)) :do (format t format-spec '((nil nil nil nil nil nil nil) (t nil nil nil nil nil nil) (nil t nil nil nil nil nil) (nil nil t nil nil nil nil) (nil nil nil t nil nil nil) (nil nil nil nil t nil nil) (nil nil nil nil nil t nil) (nil nil nil nil nil nil t ) (t t t t t t t )))))) (defun segments (digit) (case digit (0 '(t nil t t t t t)) (1 '(nil nil nil nil t nil t)) (2 '(t t t nil t t nil)) (3 '(t t t nil t nil t)) (4 '(nil t nil t t nil t)) (5 '(t t t t nil nil t)) (6 '(t t t t nil t t)) (7 '(nil nil t nil t nil t)) (8 '(t t t t t t t)) (9 '(t t t t t nil t)) (#xa '(nil t t t t t t)) (#xb '(t t nil t nil t t)) (#xc '(t t nil nil nil t nil)) (#xd '(t t nil nil t t t)) (#xe '(t t t t nil t nil)) (#xf '(nil t t t nil t nil)))) (defun print-segments (n &key (base 10.)) (check-type base (integer 2 16)) (assert (<= n (1- (expt base +digit-count+))) (n) "n must be between 0 and ~A" (1- (expt base +digit-count+))) (let ((segments (nreverse (loop :repeat +digit-count+ :for (rest digit) := (multiple-value-list (truncate n base)) :then (multiple-value-list (truncate rest base)) :collect (segments digit))))) (format t *segment-display* segments) (terpri))) #| cl-user> (print-segments 45067 :base 10) ----- ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- nil cl-user> (print-segments #xb00b5 :base 16) ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- ----- ----- nil cl-user> |#C++ solution:
#include <stdio.h> #include <stdlib.h> #include <string.h> int scanline(int i, int n) { if (i == 0) return (n >> 1) & 2; else if (i == 1) return ((n >> 3) & 1) | (n & 2) | ((n >> 2) & 4); else return ((n >> 5) & 1) | ((n << 1) & 2) | ((n >> 4) & 4); } int main(int argc, char *argv[]) { if (argc <= 1) abort(); char buff[20]; sprintf(buff, "%lu", strtoul(argv[1],0,0)); int s[] = { 125,80,55,87,90,79,111,84,127,95 }; for (int i = 0; i < 3; i++) { for (char *p = buff; *p; p++) { int n = scanline(i, s[*p - '0']); const char *c = "|_|"; for (int i = 0; i < (int)strlen(c); i++) { printf("%c", (n & (1 << i)) ? c[i] : ' '); } } printf("\n"); } }$ ./segment 1234567890 _ _ _ _ _ _ _ _ | _| _||_||_ |_ ||_||_|| | ||_ _| | _||_| ||_| _||_|Let’s try again with that sample output (at least in my browser, the last row of segments is missing):
$ ./segment 1234567890 _ _ _ _ _ _ _ _ | _| _||_||_ |_ ||_||_|| | ||_ _| | _||_| ||_| _||_|In Python
import argparse def print_digit(digit): # From digit 0-9 to bits TRANSLATE = { 0: '1011111', 1: '0000101', 2: '1110110', 3: '1110101', 4: '0101101', 5: '1111001', 6: '1111011', 7: '0010101', 8: '1111111', 9: '1111101', } # --2-- # | | # 3 4 # | | # --1-- 0 - # | | 3 | | # 5 6 6 - # | | 9 | | # --0-- 12 - # Draw each of the segments TEMPLATE = { 0: (12 + 1, '━'), 1: (6 + 1, '━'), 2: (0 + 1, '━'), 3: (3 + 0, '┃'), 4: (3 + 2, '┃'), 5: (9 + 0, '┃'), 6: (9 + 2, '┃'), } bits = TRANSLATE[digit] print_digit = 15 * [' '] segment_list = [bits[i] == '1' for i in range(7)] for index, segment in enumerate(segment_list): if segment: position, character = TEMPLATE[index] print_digit[position] = character ROW_SIZE = 3 print_digit = [print_digit[i: i + ROW_SIZE] + [' '] for i in range(0, len(print_digit), ROW_SIZE)] return print_digit def print_sequence(numbers): digits = [print_digit(number) for number in numbers] # Compose the proper lines ROWS = 5 lines = [[] for _ in range(ROWS)] for digit in digits: for line in range(ROWS): lines[line] += digit[line] total = '\n'.join(''.join(line) for line in lines) print(total) def print_number(number): numbers = [int(num) for num in str(number)] print_sequence(numbers) if __name__ == '__main__': parser = argparse.ArgumentParser(description='Print a number') parser.add_argument('numbers', metavar='N', type=int, nargs='+', help='A number to be printed') args = parser.parse_args() for number in args.numbers: print_number(number)$ python3 seven_segments.py 129485676 45 ━ ━ ━ ━ ━ ━ ━ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ━ ━ ━ ━ ━ ━ ━ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ┃ ━ ━ ━ ━ ━ ━ ━ ┃ ┃ ┃ ━ ━ ┃ ┃ ━Here’s a solution in C. calc_segments returns a 5-byte array with segment encodings. print_segments prints the segment encodings.
/* segments.c */ #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #define NUM_DIGITS 5 #define FAIL_WITH_USAGE() \ do { \ fprintf(stderr, "Usage: segments <UINT16>\n"); \ return EXIT_FAILURE; \ } while (0) // Maps each digit to a number encoding segments comprising // the digit. The rightmost bit in the 7-bit encoding corresponds // to segment 0. static uint8_t segment_lookup[] = { 0b1111101, // 0: 0,2,3,4,5,6 0b1010000, // 1: 4,6 0b0110111, // 2: 0,1,2,4,5 0b1010111, // 3: 0,1,2,4,6 0b1011010, // 4: 1,3,4,6 0b1001111, // 5: 0,1,2,3,6 0b1101111, // 6: 0,1,2,3,5,6 0b1010100, // 7: 2,4,6 0b1111111, // 8: 0,1,2,3,4,5,6 0b1011111, // 9: 0,1,2,3,4,6 }; void calc_segments(uint16_t n, uint8_t* segments) { memset(segments, 0, NUM_DIGITS); for (size_t i = 0; i < NUM_DIGITS; ++i) { uint8_t digit = n % 10; segments[NUM_DIGITS - i - 1] = segment_lookup[digit]; n /= 10; } } void print_segments(uint8_t* segments) { uint8_t positions[3][3] = { {8,2,8}, {3,1,4}, {5,0,6} }; for (size_t row = 0; row < 3; ++row) { for (size_t i = 0; i < NUM_DIGITS; ++i) { uint8_t digit_segments = segments[i]; for (size_t col = 0; col < 3; ++col) { uint8_t segment = positions[row][col]; char c = ' '; if ((digit_segments >> segment) & 1) { c = segment > 2 ? '|' : '_'; } putchar(c); } } putchar('\n'); } } int main(int argc, char* argv[]) { if (argc != 2) FAIL_WITH_USAGE(); char* n_str = argv[1]; size_t len = strlen(n_str); if (len == 0 || len > NUM_DIGITS) FAIL_WITH_USAGE(); char* endptr = NULL; long n_long = strtol(n_str, &endptr, 10); if (*endptr != '\0') FAIL_WITH_USAGE(); if (n_long < 0 || n_long > UINT16_MAX) FAIL_WITH_USAGE(); uint16_t n = (uint16_t)n_long; uint8_t segments[NUM_DIGITS]; calc_segments(n, segments); print_segments(segments); return EXIT_SUCCESS; }Example Usage:
$ ./segments 12345 _ _ _ | _| _||_||_ ||_ _| | _| $ ./segments 6789 _ _ _ _ _ | ||_ ||_||_| |_||_| ||_| _|Goofing around with Unicode operators in a Haskell solution.
{-# OPTIONS_GHC -fno-warn-type-defaults #-} import Data.Bits (setBit, zeroBits) import Data.Bool (bool) import Data.List (foldl', unfoldr, union) import Data.Tuple (swap) import Data.Word (Word8) -- Each of the following functions specifies one or more bits to set. Except -- for 0, each digit in the list of digits (further down) is defined by the -- application of three of these functions to the empty list. The functions are -- comprised of three Unicode box drawing symbols. If you align them vertically -- the three functions will look like the digit being specified. -- -- Zero is a bit unfortunate in that it's defined by just a pair of -- functions. To define its middle row we would have had to have used "┃ ┃", but -- this would be two binary operators without an intervening value, which is a -- syntax error. (╺━┓), (╺━┛), (╺━┫), (┃), (┏━╸), (┏━┓), (┏━┛), (┗━╸), (┗━┓), (┗━┛), (┗━┫), (┣━┓), (┣━┫), (╹), (╻) :: (Num a, Eq a) => [a] -> [a] -> [a] (╺━┓) = u [ 2, 4 ] (╺━┛) = u [0, 6] (╺━┫) = u [ 1, 4, 6] (┃) = u [ 4, 6] (┏━╸) = u [ 2, 3 ] (┏━┓) = u [ 2, 3, 4 ] (┏━┛) = u [ 1, 4, 5 ] (┗━╸) = u [0, 5 ] (┗━┓) = u [ 1, 3, 6] (┗━┛) = u [0, 5, 6] (┗━┫) = u [ 1, 3, 4, 6] (┣━┓) = u [ 1, 3, 5, 6] (┣━┫) = u [ 1, 3, 4, 5, 6] (╹) = u [ 6] (╻) = u [ 3 ] u :: Eq a => [a] -> [a] -> [a] -> [a] u xs ys zs = xs `union` ys `union` zs -- A visually lighter weight synonym for the empty list. o :: [a] o = [] digits :: [[Int]] digits = [o ┏━┓ o ┗━┛ o, o ┃ o ┃ o, o ╺━┓ o ┏━┛ o ┗━╸ o, o ╺━┓ o ╺━┫ o ╺━┛ o, o ╻ o ┗━┫ o ╹ o, o ┏━╸ o ┗━┓ o ╺━┛ o, o ┏━╸ o ┣━┓ o ┗━┛ o, o ╺━┓ o ┃ o ╹ o, o ┏━┓ o ┣━┫ o ┗━┛ o, o ┏━┓ o ┗━┫ o ╺━┛ o] -- A series of 8-bit words, each of which corresponds to one digit of the -- argument. Within each word the i'th bit is set if and only if the i'th -- segment of a 7-segment display would be set. bentley :: Integral a => a -> [Word8] bentley = map oneDigit . reverse . base10 where oneDigit = foldl' setBit zeroBits . (digits !!) . fromIntegral -- The list of a number's base-10 digits, from least to most significant. base10 :: Integral a => a -> [a] base10 0 = [0] base10 n = unfoldr (\i -> bool Nothing (Just . swap $ i `quotRem` 10) (i /= 0)) n main :: IO () main = do print $ bentley 3141592654 print $ bentley 78Ah well, it looks like character widths are a bit different in the browser, compared to emacs and the terminal. You’ll have to pretend that the vertical sequence of digits in the code lines up nicely…
num=’0123456789′;
def add_symbols(index,sym):
if index==0:
layer_1.append(sym)
elif index in range(1,4):
layer_2.append(sym)
else:
layer_3.append(sym)
layer_1=[]
layer_2=[]
layer_3=[]
NUMBERS = {0:’1101111′, 1:’0001001′,2:’1011110′,3:’1011011′, 4:’0111001′, 5:’1110011′,6:’1110111′,7:’1001001′,8:’1111111′, 9:’1111011′}
ONE={0:’‘, 1:’|’,2:’‘,3:’|’, 4:’|’,5:’_’,6:’|’}
[add_symbols(i,” “) if a==’0′ else add_symbols(i,ONE.get(i)) for number in num for i, a in enumerate(NUMBERS.get(int(number))) ]
print(“”.join([ ” “+i+” ” for i in layer_1]) ,end=”” )
print(”)
print(“”.join([i for index,i in enumerate(layer_2)]) ,end=”” )
print(”)
print(“”.join([ i for i in layer_3]),end=””)
_ _ _ _ _ _ _ _
| | | | _||||_ |_ |||||
|| || | | _||| ||_| _|
Here’s a solution in racket:
;; given a number 0 <= n <= 65535, turn it into a list of 5 digits (pad w/ 0 as needed) (define (number->digits num) (map (lambda (c) (- (char->integer c) 48)) (string->list (~a num #:width 5 #:align 'right #:pad-string "0")))) ;; given a number 0 <= n <= 65535, generate a list of 5 bytes w/ bits set for segment activation (define BYTES '(1111101 1010000 0110111 1010111 1011010 1001111 1101111 1010100 1111111 1011111)) (define (7seg-bytes num) (let loop ((dgts (number->digits num))) (if (empty? dgts) null (cons (list-ref BYTES (car dgts)) (loop (cdr dgts)))))) ;; given a number 0 <= n <= 65535, show the number in a 5-digit 7-segment display (define PTNS '(" " "| " " |" "| |" " ----- ")) (define SEGS '((4 3 3 3 0 3 3 3 4) (0 2 2 2 0 2 2 2 0) (4 2 2 2 4 1 1 1 4) (4 2 2 2 4 2 2 2 4) (0 3 3 3 4 2 2 2 0) (4 1 1 1 4 2 2 2 4) (4 1 1 1 4 3 3 3 4) (4 2 2 2 0 2 2 2 0) (4 3 3 3 4 3 3 3 4) (4 3 3 3 4 2 2 2 4))) (define (7seg-display num) (let ((digits (number->digits num))) (let loop ((segments "") (i 0) (j 0)) (if (> i 8) (display segments) (let* ((pattern (list-ref PTNS (list-ref (list-ref SEGS (list-ref digits j)) i))) (closer (if (< j 4) #\space #\newline)) (update (format "~a~a~a" segments pattern closer))) (if (= j 4) (loop update (add1 i) 0) (loop update i (add1 j)))))))) ;; tests ;(number->digits 0) ==> '(0 0 0 0 0) ;(number->digits 28) ==> '(0 0 0 2 8) ;(number->digits 65535) ==> '(6 5 5 3 5) ;(7seg-bytes 8863) ==> '(1111101 1111111 1111111 1101111 1010111) ;(7seg-display 8863) ==> ----- ----- ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- ----- | | | | | | | | | | | | | | | | | | | | | | | | | | | ----- ----- ----- ----- -----