The challenge is to implement the bottom encoding (only encoding, not decoding). There is a wide variety of existing implementations in the bottom-software-foundation org.

Bottom is a text encoding where each character is separated into multiple emoji.

Unicode escape(s) Character Value
U+1FAC2 ๐Ÿซ‚ 200
U+1F496 ๐Ÿ’– 50
U+2728 โœจ 10
U+1F97A ๐Ÿฅบ 5
U+002C , 1
U+2764, U+FE0F โค๏ธ 0
Unicode escape(s) Character Purpose
U+1F449, U+1F448 ๐Ÿ‘‰๐Ÿ‘ˆ Byte separator

Notes on encoding

  • The output stream will be a sequence of groups of value characters (see table above) with each group separated by the byte separator character, i.e.
  • The total numerical value of each group must equal the decimal value of the corresponding input byte.
    • For example, the numerical value of ๐Ÿ’–๐Ÿ’–,,,,, as according to the character table above, is 50 + 50 + 1 + 1 + 1 + 1, or 104. This sequence would thus represent U+0068 or h, which has a decimal value of 104.
    • Note the ordering of characters within groups. Groups of value characters must be in descending order. While character order (within groups) technically does not affect the output in any way, arbitrary ordering can encroach significantly on decoding speed and is considered both illegal and bad form.
  • Byte separators that do not follow a group of value characters are illegal, i.e ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ‘‰๐Ÿ‘ˆ or ๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆ. As such, ๐Ÿ‘‰๐Ÿ‘ˆ alone is illegal.
  • Groups of value characters must be followed by a byte separator. ๐Ÿ’–๐Ÿ’–,,,, alone is illegal, but ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆ is valid.
  • The null value must not be followed by a byte separator. ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆ and ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธ alone are valid, but ๐Ÿ’–๐Ÿ’–,,,,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธ๐Ÿ‘‰๐Ÿ‘ˆ is illegal.

Some pseudocode to illustrate how each character is converted:

for b in input_stream:
    # Get `b`'s UTF-8 value
    let v = b as number
    let o = new string

    if v == 0:
        while true:
            if v >= 200:
                v = v - 200
            else if v >= 50:
                v = v - 50
            else if v >= 10:
                v = v - 10
            else if v >= 5:
                v = v - 5
            else if v >= 1:
                v = v - 1


return o


  1. The standard I/O rules apply.
  2. The input must be a string, not a list of bytes.
  3. An empty string should return an empty string.
  4. It should return the same result as bottom-web for any input with no null bytes (UTF-8 validation is not required).

Some test cases:

  • test -> ๐Ÿ’–๐Ÿ’–โœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจ๐Ÿฅบ๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ
  • Hello World! -> ๐Ÿ’–โœจโœจ,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจ,๐Ÿ‘‰๐Ÿ‘ˆโœจโœจโœจ,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–โœจโœจโœจ๐Ÿฅบ,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ‘‰๐Ÿ‘ˆโœจโœจโœจ,,,๐Ÿ‘‰๐Ÿ‘ˆ
  • ๐Ÿฅบ -> ๐Ÿซ‚โœจโœจโœจโœจ๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–๐Ÿฅบ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจ๐Ÿฅบ๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ
  • (2 spaces) -> โœจโœจโœจ,,๐Ÿ‘‰๐Ÿ‘ˆโœจโœจโœจ,,๐Ÿ‘‰๐Ÿ‘ˆ
  • ๐€Š -> ๐Ÿซ‚โœจโœจโœจโœจ๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจโœจ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจ๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจ๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ
  • โ‰โƒ  -> ๐Ÿซ‚โœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจโœจ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจ๐Ÿฅบ,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿซ‚โœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจ๐Ÿ‘‰๐Ÿ‘ˆ
  • Xโƒฉ -> ๐Ÿ’–โœจโœจโœจ๐Ÿฅบ,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿซ‚โœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจ๐Ÿฅบ,,,,๐Ÿ‘‰๐Ÿ‘ˆ
  • โค๏ธ -> ๐Ÿซ‚โœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–๐Ÿฅบ,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿซ‚โœจโœจโœจ๐Ÿฅบ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–๐Ÿ’–โœจโœจโœจ,,,,๐Ÿ‘‰๐Ÿ‘ˆ๐Ÿ’–๐Ÿ’–โœจโœจโœจโœจ,,,๐Ÿ‘‰๐Ÿ‘ˆ
  • .\0. (null byte surrounded by full stops) -> โœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธโœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ

This is , so fewest bytes win!

  • \$\begingroup\$ Is it allowed to take the input string as a list of byte values? \$\endgroup\$ Feb 4, 2021 at 12:49
  • \$\begingroup\$ Added this to the rules. I think forcing a string makes more sense so it's more consistent but not sure if some languages struggle converting it to bytes \$\endgroup\$
    – Orangutan
    Feb 4, 2021 at 13:03
  • 1
    \$\begingroup\$ Why is โค๏ธ not followed by ๐Ÿ‘‰๐Ÿ‘ˆ in the new test case? \$\endgroup\$
    – Arnauld
    Feb 4, 2021 at 13:25
  • \$\begingroup\$ The bytes separator is used to see the separation between bytes. When you're decoding and you see a null byte representation, you know there's exactly 1 null byte as for it to be added the value must be exactly 0 so the bytes separator isn't needed to decode the heart. This is actually a gap in the spec and reference implementation. Using bottom web you can encode something then decode the output and get an error due to this inconsistency. Based on the pseudocode given in the spec and the reference implementation's encode, I would say this test case is the most accurate interpretation \$\endgroup\$
    – Orangutan
    Feb 4, 2021 at 13:37
  • \$\begingroup\$ @Orangutan Bottom-web doesn't decode โœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธโœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ properly, but it does for โœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธ๐Ÿ‘‰๐Ÿ‘ˆโœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ. Also, โœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธโœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ violates the EBNF syntax you yourself specified. \$\endgroup\$
    – Makonede
    Feb 4, 2021 at 18:52

8 Answers 8


APL(Dyalog Unicode), 133 bytes SBCS


Try it on APLgolf!

-5 thanks to ovs.

  • \$\begingroup\$ This question uses the bytes in the UTF-8 encoding of the input, which is different from this codepoint. You can get this with 'UTF-8'โŽ•ucsโต: Slightly golfed \$\endgroup\$
    – ovs
    Feb 4, 2021 at 12:41
  • \$\begingroup\$ And I don't think these emojis are included in the single-byte code page, which means you can't encode the program with this. You either have to encode it in UTF-8, which is quite long, or remove those. \$\endgroup\$
    – ovs
    Feb 4, 2021 at 12:43
  • \$\begingroup\$ hmm, fair, i overlooked that and just trusted the byte counter. \$\endgroup\$ Feb 4, 2021 at 12:44
  • \$\begingroup\$ @Razetime doesn't work \$\endgroup\$ Feb 4, 2021 at 12:53

05AB1E, 101 100 90 86 bytes

โ€™inรˆยฆt("รฟ",รฎยณaries: 1)โ€™.E#ฮตรพD_iโ€ขyร‘รกรœโ€ข2รครงJรซโ€ข$Hรด}โ€ขฦต&ะฒvyโ€ฐ`}1D)โ€ข}ร‘ยชโ€”ฤ†ล“ล’รœ{ยนโ€™ฮธวโ‚‚?โ€ขโ€ข1รพร€โ€ขะฒรงร—]S

Output as a list of characters.

-10 bytes thanks to @ovs.

Try it online or verify all test cases.


โ€™inรˆยฆt("รฟ",รฎยณaries: 1)โ€™  # Push dictionary string "inspect("รฟ",binaries: 1)",
                         # where `รฟ` is automatically replaced with the (implicit) input
  .E                     # Execute it as Elixir code
    #                    # Split the result in spaces
     ฮต                   # Map over each string-part:
      รพ                  #  Only leave the digits of the string
       D_i               #  If it's 0 (thus a null-byte):
          โ€ขyร‘รกรœโ€ข         #   Push compressed integer 1008465039
                2รค       #   Split it into 2 parts: [10084,65039]
                  รง      #   Convert each to a character with this codepoint: ["โค","๏ธ"]
                   J     #   Join the inner pair together: "โค๏ธ"
         รซ               #  Else: 
          โ€ข$Hรด}โ€ข         #   Push compressed integer 1626142265
                ฦต&       #   Push compressed integer 201
                  ะฒ      #   Convert 1626142265 to base-201 as list: [200,50,10,5]
                   v     #  Loop over each integer `y`:
                    yโ€ฐ   #   Take divmod-`y` on the top integer
                         #   (which is the codepoint in the first iteration)
                      `  #   Pop and push the pair separated to the stack: n//y and n%y
                   }1D   #  After the loop: push two 1s
                      )  #  And wrap everything into a list
                         #  Push compressed integer 618452457360154459249099105698966985
             โ€ข1รพร€โ€ข       #  Push compressed integer 129731
                  ะฒ      #  Convert the larger integer to base-129731 as list:
                         #   [129730,128150,10024,129402,44]
                   รง     #  Convert each to a character:
                         #   ["๐Ÿซ‚","๐Ÿ’–","โœจ","๐Ÿฅบ",",","๐Ÿ‘‰","๐Ÿ‘ˆ"]
                    ร—    #  Repeat each character the divmod-list amount of times
     ]                   # Close the if-else statement and map
      S                  # Convert the list of lists to a flattened list of characters
                         # (after which the result is output implicitly)

See this 05AB1E tip of mine (sections How to use the dictionary?; How to compress large integers?; and How to compress integer lists?) to understand why โ€™inรˆยฆt("รฟ",รฎยณaries: 1)โ€™ is "inspect("รฟ",binaries: 1)"; โ€ขyร‘รกรœโ€ข is 1008465039; โ€ข$Hรด}โ€ข is 1626142265; ฦต& is 201; โ€ข$Hรด}โ€ขฦต&ะฒ is [200,50,10,5]; โ€ข}ร‘ยชโ€”ฤ†ล“ล’รœ{ยนโ€™ฮธวโ‚‚?โ€ข is 618452457360154459249099105698966985; โ€ข1รพร€โ€ข is 129731; and โ€ข}ร‘ยชโ€”ฤ†ล“ล’รœ{ยนโ€™ฮธวโ‚‚?โ€ขโ€ข1รพร€โ€ขะฒ is [129730,128150,10024,129402,44,128073,128072].

  • 1
    \$\begingroup\$ The reason this doesn't work on TIO is that 05AB1E now uses Elixir to evaluate the expression: Try it online!. (This is the shortest way I could find) \$\endgroup\$
    – ovs
    Feb 4, 2021 at 16:21
  • \$\begingroup\$ @ovs Oh, didn't knew .E worked as an Elixir-eval now. Is that the new functionality in the new 05AB1E, or only on TIO? It does make sense, but I thought .E was still a Python eval despite 05AB1E being build in Elixir now. But apparently not? \$\endgroup\$ Feb 4, 2021 at 16:35
  • \$\begingroup\$ @ovs I've changed the entire program to Elixir evals now. Unfortunately it's quite a bit longer, but it works on TIO now, so whatever. Also, these are slightly shorter, but thanks for the "inspect("ÿ",binaries: 1). :) \$\endgroup\$ Feb 4, 2021 at 17:52
  • \$\begingroup\$ The .E command has been like this since July of 2018, maybe .E might have been Python-eval for a short time, but now its Elixir's Code.eval_string only. \$\endgroup\$
    – ovs
    Feb 4, 2021 at 17:53
  • 1
    \$\begingroup\$ You can generate the โค๏ธ emoji with โ€ขyÑáÜโ€ข2äçJ which is 10 bytes shorter ;) \$\endgroup\$
    – ovs
    Feb 4, 2021 at 17:58

Python 3, 134 130 bytes

-4 bytes thanks to bb94!

for v in open(0,'rb').read():print(v//200*'๐Ÿซ‚'+v//50%4*'๐Ÿ’–'+v//10%5*'โœจ'+v//5%2*'๐Ÿฅบ'+v%5*',',end=0**v*'โค๏ธ'or'๐Ÿ‘‰๐Ÿ‘ˆ')

Try it online! Test case includes a trailing null byte.

open(0,'rb').read() is the exact same length as bytes(input(),'u8'), but reads the entire input instead of one line.

  • \$\begingroup\$ There was an indent issue with the pseudocode, if there is a 0 byte it should still add a byte separator. bottom-web should be considered the main test \$\endgroup\$
    – Orangutan
    Feb 4, 2021 at 13:05
  • \$\begingroup\$ Also reading from stdin and outputting to stdout isn't required due to the standard I/O rule so you could use lambda instead \$\endgroup\$
    – Orangutan
    Feb 4, 2021 at 13:08
  • \$\begingroup\$ @Orangutan bottom-web doesn't add a byte seperator after a null byte though. The output for .\0. is โœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆโค๏ธโœจโœจโœจโœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ. (The shortest lambda I found was 136 bytes) \$\endgroup\$
    – ovs
    Feb 4, 2021 at 13:10
  • \$\begingroup\$ Actually never mind my comment, yeah no byte separator needed as you don't need to know how many 0s there are \$\endgroup\$
    – Orangutan
    Feb 4, 2021 at 13:12
  • 2
    \$\begingroup\$ Couldn't you use v//50%4, v//10%5, and v//5%2 instead of v%200//50, v%50//10, and v%10//5 to save some bytes? \$\endgroup\$
    – bb94
    Feb 5, 2021 at 1:19

JavaScript (newer browsers), 166 164 bytes

s=>[...new TextEncoder().encode(s)].map(c =>(`๐Ÿซ‚`[r=`repeat`](c/200)+`๐Ÿ’–`[r](c/50%4)+`โœจ`[r](c/10%5)+`๐Ÿฅบ`[r](c/5%2)+`,`[r](c%5)||`โค๏ธ`)+`๐Ÿ‘‰๐Ÿ‘ˆ`).join``
<input oninput=o.textContent=f(this.value)><div id=o>

(Only the second line qualifies as the code; the rest is just part of the Stack Snippet.)

Can I Use says that Firefox supported TextEncoder before arrow functions, but I didn't bother to check other browser support.

  • \$\begingroup\$ TextEncoder is browser API, not a part of ES standard. Maybe you can claim the language as JavaScript (browser) or something like it. \$\endgroup\$
    – tsh
    Feb 5, 2021 at 3:48

Wolfram Language (Mathematica), 141 bytes


Try it online!


JavaScript (Node.js), 133 bytes


Try it online!


Raku, 110 112 bytes

{[~] map ->\a {[~] "โค๏ธ๐Ÿซ‚๐Ÿ’–โœจ๐Ÿฅบ,๐Ÿ‘‰๐Ÿ‘ˆ".comb Zx(!a,a/200,a/50%4,a/10%5,a/5%2,a%5,?a,?a)},@(.encode)}

+2 bytes to properly implement null byte encoding

Try it online!


Lua (LuaJIT), 184 172 158 bytes

r=s.rep;s:gsub('.',function(v)v=v:byte()io.write(r('๐Ÿซ‚',v/200),r('๐Ÿ’–',v/50%4),r('โœจ',v/10%5),r('๐Ÿฅบ',v/5%2),r(',',v%5),v~=0 and'๐Ÿ‘‰๐Ÿ‘ˆ'or'โค๏ธ')end)

Try it online!


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.