I recently stumbled across this image on wikimedia commons. It's a little bit of an information overload at first, but after examining it a bit it shows an interesting number system for writing nibbles.

Tesseract Hasse diagram with nibble shorthands

Image created by user Watchduck.

First off a "nibble" is a 4 bit number which is really a number ranging from 0 to 15, with a particular emphasis on binary. This diagram shows a shorthand for writing such numbers with each number being one symbol. Ok, who cares? Plenty of people make up new symbols for numbers all the time. Well the interesting thing about these numbers is their symmetry.

If you take one of the symbols and mirror it horizontally you get the symbol of the number with the same bits in reverse order. For example the symbol that sort of looks like a 3 represents the bits 0011, it's horizontal mirror represents the bits 1100. Numbers which are symmetric along this mirror represent nibbles which are palindromes.

Similarly if you take a symbol and rotate it a half turn (180 degrees) you get the symbol for the bitwise compliment of that number, for example if you take the symbol that looks like a 7, it represents 0111, if you rotate it a half turn you get a symbol representing 1000.


You will write a program or function which takes as input a nibble and outputs an image of a symbol. Your symbols are not required to be anything in specific but they are required to have the symmetry properties.

  1. Distinct nibbles should give distinct symbols
  2. The symbol for a nibble \$r\$ should be the mirror image of the symbol for the reverse of \$r\$.
  3. The symbol for a nibble \$r\$ should be the symbol for the bitwise compliment of \$r\$ rotated 180 degrees.

You may take input in any reasonable format and output in any image or graphical format. There are no additional requirements on resolution, aside from those implied by the rules. You can make your symbols as small as you wish as long as you satisfy the rules.

This is the goal is to minimize the size of your source code as measured in bytes.

  • \$\begingroup\$ Are Unicode characters (such as the ones from the Geometric Shapes block) a valid/reasonable output format for this challenge? \$\endgroup\$
    – Arnauld
    Sep 27, 2022 at 14:42
  • 1
    \$\begingroup\$ @Arnauld That's a forbidden loophole \$\endgroup\$
    – Wheat Wizard
    Sep 27, 2022 at 14:43
  • \$\begingroup\$ How characters are rendered is entirely up to the font in use. Is p the mirror image of q? It depends on the font. You can maybe say an answer in X language + Y font satisfies the requirement, but at that point it would really just be making a programming language to trivialize the problem. The challenge is graphical-output that's what it's about. \$\endgroup\$
    – Wheat Wizard
    Sep 27, 2022 at 14:50
  • \$\begingroup\$ So, to be clear, text output is entirely forbidden unless it's part of an image? \$\endgroup\$ Sep 27, 2022 at 15:06
  • \$\begingroup\$ Can the nibble be taken as a binary array with 4 digits? That is, [0 0 1 1] for 3 \$\endgroup\$
    – Luis Mendo
    Sep 27, 2022 at 15:48

11 Answers 11


MATL, 12 7 bytes


Input is a 4-digit binary vector.

Example outputs (or try it at MATL Online!):

  • Input [0 1 0 0]:

enter image description here

  • Input [1 0 1 1]:

enter image description here

  • Input [1 1 0 1]:

enter image description here

  • Input [1 0 0 1]:

enter image description here

  • Input [0 1 1 0]:

enter image description here

How it works

The output is a 2-row, 4-column image where the first row corresponds to the input binary digits, and the second row is the first element-wise negated and reversed.

t     % Implicit input. Duplicate
      % STACK: [0 1 0 0], [0 1 0 0]
~     % Negate, element-wise
      % STACK: [0 1 0 0], [1 0 1 1]
P     % Reverse
      % STACK: [0 1 0 0], [1 1 0 1]
v     % Vertically concatenate
      % STACK: [0 1 0 0; 1 1 0 1]
1YG   % Display image with scaled colors. 0 is shown as black, 1 as white
  • \$\begingroup\$ Both 0000 and 1111 are just a horizontal bar. Both 0011 and 1100 are just a square. Yes, they are in different positions in regard to a background grid, so the answer is formally correct. But would you accept them as distinct "characters" if this was a script? \$\endgroup\$
    – ccprog
    Sep 27, 2022 at 20:57
  • 3
    \$\begingroup\$ @ccprog All inputs produce a 2x4 image. There is no background, just a 2x4 image in which each of the 8 pixels is either black or white \$\endgroup\$
    – Luis Mendo
    Sep 27, 2022 at 21:17
  • \$\begingroup\$ Yes, there is no grid. What I meant is that you would need one to securely distinguish between the symbols. Imagine using these images to write the hex number sequences 5BA and A35. Would you be able to read them correctly? Again, this was not part of the question, but I wonder if there are solutions that combine your algorithmic simplicity with the visual clearness of Jordan's answer. \$\endgroup\$
    – ccprog
    Sep 27, 2022 at 21:38
  • 1
    \$\begingroup\$ Oh, oh, the first one should have been 5CA (blush), and I was assuming horizontal stacking and a distance between the individual parts, just as if the single images were digits from a font. If you stack vertically, the distinction between 0 and F would be the one difficult to read. Key point: in a longer vertical sequence, would you be able to see if the line sits low or high, and in a horizontal sequence, if the square (or the two short vertical lines of 5 and C) sits left or right? - I use "symbol" just as in the question, image = symbol ( = glyph, if you prefer the typografical term). \$\endgroup\$
    – ccprog
    Sep 27, 2022 at 23:02
  • 1
    \$\begingroup\$ @ccprog I think I see what you mean: “reading” a sequence of individual images as if they were letters read by a person. Then yes, I agree that more discernible symbols (individual images) would be preferred. Thanks for clarifying \$\endgroup\$
    – Luis Mendo
    Sep 27, 2022 at 23:13

Ruby, 106 bytes

Takes an integer as input and returns a PBM image.

smR\rzrt"
5 3

Attempt This Online!

The code has unprintable characters that make it copy-and-paste-unfriendly; expand the below snippet for an xxd dump if you want to try it on your own machine.

00000000: 2d3e 697b 743d 2200 1701 2702 2f03 2504  ->i{t="...'./.%.
00000010: 5b05 6706 7c07 6508 5309 1f0a 730b 6d0c  [.g.|.e.S...s.m.
00000020: 525c 727a 0e72 0f74 220a 2250 310a 3520  R\rz.r.t"."P1.5 
00000030: 330a 222b 2822 2530 3862 2225 745b 742e  3."+("%08b"%t[t.
00000040: 696e 6465 7828 692e 6368 7229 2b31 5d2e  index(i.chr)+1].
00000050: 6f72 6429 2e67 7375 6228 2f2e 2f2c 2726  ord).gsub(/./,'&
00000060: 3027 295b 312e 2e5d 7d0a                 0')[1..]}.

The shorthand

How it works

The images are 5×3 but there are only 7 "real" pixels, in a hex pattern:

 1 1
1 1 1
 1 1

This makes each image's data fit in a byte. t is a lookup table where the ASCII character corresponding to each nibble is followed by the character corresponding to its image representation. The code looks up the image data in the table, formats it as a string of 1s and 0s, interleaves the extra 0s, and returns it in PBM format.


  table = "\x00\x17\x01\x27\x02\x2f\x03\x25\x04\x5b\x05\x67\x06\x7c\x07\x65\x08\x53\x09\x1f\x0a\x73\x0b\x6d\x0c\x52\x0d\x7a\x0e\x72\x0f\x74"
  image_byte = table[table.index(input.chr) + 1].ord
  image_data = ("%08b" % image_byte).gsub(/./,'\&0')[1..]

5 3
" + image_data

Python 3 + NumPy and Pillow, 101 92 91 82 77 bytes

import numpy as n,PIL.Image as p
lambda x:p.fromarray(n.array([x,1-x[::-1]]))

Takes in a NumPy array of ones and zeros and returns the resulting 4x2 image. Basically a copy of Luis Mendo's answer in Python. Ungolfed:

import numpy as np
from PIL import Image

def represent(nibble: np.array) -> None:
    representation = np.array([nibble, 1 - nibble[::-1])
    image = Image.fromarray(representation)
    return image

Thanks to Louis Mendo, who figured this out (and then saved me 9 bytes)!


  • [1, 0, 0, 1]

first example

  • [1, 1, 1, 1]

second example

  • [0, 1, 1, 0]

third example

I have no idea where the blurring comes from. However, it still satisfies the criteria. Note that in these images, the colors are RGB (0, 0, 0) and (255, 255, 255), for human readability. However, in the image generated by the program, the colors are (0, 0, 0) and (1, 1, 1), which, while distinct, cannot be told appart using a human eye.

  • -9 thanks to Luis Mendo
  • -1 thanks to Jonathan Allen
  • -9 because I figured out I could return the image instead of displaying it, and not assign this to a variable
  • -5 again thanks to Jonathan Allen
  • 2
    \$\begingroup\$ @LuisMendo Thanks, that reduces the code by 9 bytes \$\endgroup\$
    – pigrammer
    Sep 27, 2022 at 18:02
  • 1
    \$\begingroup\$ (1-x)[::-1] -> -~-x[::-1] (yep - and ~ should work with NumPy arrays). \$\endgroup\$ Sep 27, 2022 at 19:24
  • 1
    \$\begingroup\$ @JonathanAllan Thanks. I'd originally tried ~x instead of 1-x but Python's bitwise not (annoyingly) works with unsigned integers. \$\endgroup\$
    – pigrammer
    Sep 27, 2022 at 19:27
  • 1
    \$\begingroup\$ ...oh, hang on, you can just do 1-x[::-1] :) \$\endgroup\$ Sep 27, 2022 at 19:46
  • 2
    \$\begingroup\$ I wonder if you really need the 255* too? \$\endgroup\$ Sep 27, 2022 at 19:47

JavaScript (browser), 187 182 bytes

(n,c)=>c.getContext`2d`.putImageData(new ImageData(new Uint8ClampedArray(Int32Array.from([...s=n.toString(2).padStart(4,0),...[...s].reverse()],(c,i)=>-1<<(c^i/4)*24).buffer),4),0,0)
<input type=number size=2 min=0 max=15 oninput=f(+this.value,c)><div><canvas id=c height=2 width=4>

Uses @LuisMendo's method. Draws the output on a 2×4 canvas passed as the second parameter, but the snippet scales the output up 64 times for convenience.

  • \$\begingroup\$ You're missing the negation step for the second 4. \$\endgroup\$
    – ccprog
    Sep 28, 2022 at 1:11
  • \$\begingroup\$ you can curry the function with n=>c=> instead of (n,c)=> for -1 \$\endgroup\$
    – naffetS
    Sep 28, 2022 at 1:17
  • \$\begingroup\$ @ccprog Thanks, fixed. \$\endgroup\$
    – Neil
    Sep 28, 2022 at 6:19
  • 1
    \$\begingroup\$ @thejonymyster see first comment. \$\endgroup\$
    – Neil
    Sep 28, 2022 at 6:20
  • 1
    \$\begingroup\$ @ccprog Functions may output by modifying their arguments. \$\endgroup\$
    – Neil
    Sep 28, 2022 at 20:07

Wolfram Language (Mathematica), 23 bytes

A port of @Luis Mendo's MATL answer.


enter image description here

  • 1
    \$\begingroup\$ Image@{#,1-Reverse@#}& saves one byte. If we represented the bits as -1 and 1 rather than 0 and 1, then we could do Image@{#,-Reverse@#}&, but that might be bending the rules somewhat. \$\endgroup\$ Sep 30, 2022 at 16:15

Jelly, 12 bytes


A full program that outputs a PBM file on STD OUT.

Try it online!


Jelly cannot produce images on screen, so this places an image file content on STD OUT ready to be directed to a file.

The PBM format idea came from Jordan's Goruby and Ruby posts. Go upvote!

The method is similar to Luis Meno's MATL post, because there is nothing simpler. Go upvote!

The only difference is that rather than the number on top of the reversed negated number we have the reverse of the number on top of the negated number.

142D;Ṛ;¬K”P; - Main Link: list of four bits, N     e.g. [0,0,1,0]
142          - 142
   D         - to decimal digits -> [1, 4, 2]
     Ṛ       - reverse (N)                              [0,1,0,0]
    ;        - concatenate                              [1,4,2,0,1,0,0]
       ¬     - logical NOT (N)                          [1,1,0,1]
      ;      - concatenate                              [1,4,2,0,1,0,0,1,1,0,1]
        K    - join with spaces                         [1,' ',4,' ',2,' ',0,' ',1,' ',0,' ',0,' ',1,' ',1,' ',0,' ',1]
         ”P  - 'P'
           ; - concatenate                              ['P',1,' ',4,' ',2,' ',0,' ',1,' ',0,' ',0,' ',1,' ',1,' ',0,' ',1]
             - implicit, smashing print                 P1 4 2 0 1 0 0 1 1 0 1
                                                        i.e. the pixels:

HTML + Javascript 70 + 159 154 151 = 221 bytes

The output is topologically like Jordan's, with the output rotated by 90° and lines instead of dots. This achieves a classical seven part display. The symbols also loosely resemble those used by Watchduck in his original grafic in shape and order.

The order of the elements is

  0            M1,1h3
1   2      M1,1v3  M4,1v3
  3             M1,4h3
4   5      M1,4v3  M4,4v3
  6            M1,7h3

After experimenting with multiple other variants, I find it interesting that this intuitive ordering still gives the shortest path production.

The Bigint holds the shown elements for each symbol as bits ordered right to left. Since the symbol for 1111, the last symbol, has no shown elements in the lower half, the last (leftmost) hex digit is 0, and the number can be written in hex with 27 digits instead of the expected 16*7/4 = 28.

-5 bytes thanks to Steffan.

-3 bytes for bitwise operation on BigInt and symbol reordering.

<input oninput=f(value)><svg viewBox=0,0,5,8><path id=p stroke=#000 />

Ungolfed and a bit more pretty

f = n => {
    t = 0xe5cfdb4f4dbf24fdb2f2dbf3a70n
    for (d='',i=0; i<7; i++) {
        if (t>>BigInt(n*7+i)&1n) {
            d += `M${(i+1)%3?1:4},${(i/3&3)*3+1}${i%3?'v':'h'}3`
    p.setAttribute('d', d)
<input type=number size=2 value=0 min=0 max=15 oninput=f(value)>
  <svg viewBox=0,0,5,8 width=80>
    <path id=p stroke=#000 />

  • \$\begingroup\$ You don't need to convert 0xca9d787f8ded97e9b7b1fe1eb953n.toString(2) to a list. (aka remove [... and ]) \$\endgroup\$
    – naffetS
    Sep 29, 2022 at 18:06

Goruby, 37 bytes

Takes an array of 0s and 1s and returns a PBM image.

Same technique as Luis Mendo's answer.

4 2

Attempt This Online!


Desmos, 44 bytes


I have no idea what this question is asking about; I'm just porting most of the other answers here.

Try It On Desmos!

Try It On Desmos! - Prettified


Fig, \$16\log_{256}(96)\approx\$ 13.17 bytes

J/Pj/ JJf142$xe!

Try it online!

Outputs as a PBM image. Port of Jelly, so go upvote that (and the posts that inspired it)!

  • \$\begingroup\$ How is it that the bit count is fractional (13.17*8=105.36 bits)? \$\endgroup\$
    – Jachdich
    May 21, 2023 at 13:24
  • \$\begingroup\$ @Jachdich People decided to allow fractional bits, whether they exist or not codegolf.meta.stackexchange.com/questions/24251/…. I disagree but I can't change the rules. \$\endgroup\$
    – naffetS
    May 21, 2023 at 14:23

K (ngn/k), 35 33 bytes

{`0:"P1\n4 2\n",*',/$x,"\n",$|~x}

Try it online!

Took me quite some time, but I finally got it. Output PBM image data, as that seems to be what everyone's doing.

Forgive me if the explanation doesn't make sense. Neither do I understand it well.


{`0:"P1\n4 2\n",*',/$x,"\n",$|~x}    Main program. Takes x as input
                               x     x itself
                             |~      Not, then reverse
                            $        Convert each number to string
                       "\n",         Concat with newline
                      ,              Concat with
                    $x               x, with each number converted to string
                  ,/                 Joined everything together
                *'                   Get the first element of each string.
                                     This is to remove the sub-arrays of the negate-then-reverse x
               ,                     Concat the entire result with
    "P1\n4 2\n"                      PBM hardcoded image data. Defined an image of 2x4 pixels
 `0:                                 Print everything to stdout

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.