10
\$\begingroup\$

Challenge

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:
        o.append("❤️")
    else:
        while true:
            if v >= 200:
                o.append("🫂")
                v = v - 200
            else if v >= 50:
                o.append("💖")
                v = v - 50
            else if v >= 10:
                o.append("✨")
                v = v - 10
            else if v >= 5:
                o.append("🥺")
                v = v - 5
            else if v >= 1:
                o.append(",")
                v = v - 1
            else:
                break

        o.append("👉👈")

return o

Rules

  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!

\$\endgroup\$
7
  • \$\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

2
\$\begingroup\$

APL(Dyalog Unicode), 133 bytes SBCS

{↑,/{⍵=0:'❤️'⋄'👉👈',⍨⍬{⍵≥200:(⍺,'🫂')∇⍵-200⋄⍵≥50:(⍺,'💖')∇⍵-50⋄⍵≥10:(⍺,'✨')∇⍵-10⋄⍵≥5:(⍺,'🥺')∇⍵-5⋄⍵≥1:(⍺,',')∇⍵-1⋄⍺}⍵}¨'UTF-8'⎕ucs⍵}

Try it on APLgolf!

-5 thanks to ovs.

\$\endgroup\$
4
  • \$\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
2
\$\begingroup\$

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.

Explanation:

’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].

\$\endgroup\$
8
  • 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
2
\$\begingroup\$

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.

\$\endgroup\$
10
  • \$\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
2
\$\begingroup\$

JavaScript (newer browsers), 166 164 bytes

f=
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.

\$\endgroup\$
1
  • \$\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
1
\$\begingroup\$

Wolfram Language (Mathematica), 141 bytes

Table@@@({Characters@"🫂💖✨🥺,",#~NumberDecompose~{200,50,10,5,1}})<>If[#>0,"👉👈","❤️"]&/@Normal@StringToByteArray@#<>""&

Try it online!

\$\endgroup\$
0
\$\begingroup\$

JavaScript (Node.js), 133 bytes

s=>[...Buffer(s)].map(n=>n?[200,50,10,5,1].map((v,i)=>[..."🫂💖✨🥺,"][i].repeat(n/v,n%=v)).join``+"👉👈":"❤️").join``

Try it online!

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$
0
\$\begingroup\$

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!

\$\endgroup\$

Your Answer

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

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