10
\$\begingroup\$

Problem

You are tasked with creating a program that performs emoji encryption on a given string of emojis. In this encryption scheme, each emoji is replaced by a unique character (from a to z). Your program should take an emoji string as input and output the corresponding encrypted string.

The mapping of emojis to characters is up to you and can vary each time the program is run. For example:

Input: πŸŒŸπŸŒŸπŸŒŸπŸŒΊπŸŒΊπŸ˜πŸŽ‰πŸ˜πŸ”
Output 1: aaabbcdce
Output 2: xxxiikbkg

Both outputs are valid.

Your task is to write a program that produces encrypted strings for given emoji inputs. Your program will be evaluated based on its average result value and size of code (in bytes).

Value of the Result

Each letter from a to z has a value from 0 to 25 in ascending order. ie.

a = 0
b = 1
c = 2 
  .
  .
  .
y = 24
z = 25

The value of the result is calculated as the summation of the values of all characters in the resulting string. For example:

aaabbcdce
0 + 0 + 0 + 1 + 1 + 2 + 3 + 2 + 4 = 13

Scoring Criteria:

Run the program 10,000 times, each time generating a random 25-emoji string, and calculate the average value of the results. Your score will be determined by this average value in comparison with the size of the code.

The goal is to create a program that, on average, produces encrypted strings with the lowest possible value based on the emoji-to-character mapping chosen for each run (a run is considered converting 25 emojis to a string hence there will be 10,000 unique runs considered in your score)

Create and submit a program that generates encrypted strings as described above, and your score will be the average result value obtained from the 10,000 runs and multiply by the size of the code in bytes. A lower Score is better.

$${\text{average value of results over }10,000\text{ runs}}\times {\text{size of code in bytes}} $$ LOWER SCORE IS BETTER

Notes:

Ignore decryption

This problem emphasizes randomness and the ability to produce low-value results on average, so figuring out which emoji has been repeated the most in the input and giving it the character a will improve the program's result.

Input restrictions

  • Input Emoji string will contain 25 emojis without any spaces.
  • There will only be 10 unique emojis in the Input at maximum.
  • The 10 valid emojis in the Input are πŸ—“πŸ™πŸ“—πŸ’»πŸ“‡πŸš’πŸ‰πŸ’˜πŸ˜ͺπŸŽ‰

some example inputs:

Invalid input: πŸ—“πŸ™πŸšπŸΏπŸ’»πŸπŸ’£πŸ“‘πŸ“‡πŸ““πŸ‰πŸ™…πŸŽ—β™ΏοΈπŸ‘Όβ›·πŸ””πŸ’˜5οΈβƒ£βπŸ“—πŸŒ‘πŸ˜ͺπŸ‘’πŸš’ (it has more than 10 unique emojis)
               πŸ—“πŸ™πŸšπŸΏπŸ’»πŸ πŸ“‘πŸ“‡πŸ““πŸ‰πŸ™…πŸŽ—β™ΏοΈπŸ‘Όβ›·  5οΈβƒ£βπŸ“—πŸŒ‘πŸ˜ͺπŸ‘’πŸš’ (invalid for including spaces)

Valid input: πŸ—“πŸ™πŸ“—πŸ“—πŸ’»πŸ—“πŸ“‡πŸš’πŸ“‡πŸ—“πŸ‰πŸ—“πŸ—“πŸ—“πŸ“‡πŸš’πŸ“‡πŸ’˜πŸ“—πŸ“—πŸ“—πŸ—“πŸ˜ͺπŸ“‡πŸš’ ( Valid since it has only 9 unique emojis (<= 10) and no spaces)
Valid output to this Input: abccdaefeagaaaefehcccaief

example random Input generators

Python

import random

input_string = ''.join(random.choice("πŸ—“πŸ™πŸ“—πŸ’»πŸ“‡πŸš’πŸ‰πŸ’˜πŸ˜ͺπŸŽ‰") for _ in range(25))

print("Emoji String:", input_string)
\$\endgroup\$
12
  • \$\begingroup\$ Should glyphs consisting of multiple characters, like flags or skin tone modifiers, be considered one or multiple emojis? Or can we assume those won't be in the input? \$\endgroup\$
    – mousetail
    Aug 20, 2023 at 13:25
  • \$\begingroup\$ they are considered different, but also it doesn't matter @mousetail as generating the input is not part of the code so you could just choose a random set of 10 emojis that do not include the human ones. Because the only restriction really is that the distribution should be uniform and I don't really know if the skin tones are considered different or similar in your random function. \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 13:29
  • 2
    \$\begingroup\$ You said the input is given though, so I don't understand how it does not matter. You need to specify what possible inputs can be \$\endgroup\$
    – mousetail
    Aug 20, 2023 at 13:32
  • 1
    \$\begingroup\$ dividing byte the size of the code insentiences longer programs (with no lower bound on the score), did you mean "multiply by the size of the source-code"? \$\endgroup\$
    – bsoelch
    Aug 20, 2023 at 13:53
  • 1
    \$\begingroup\$ wait i just realized the problem. yea I should change it to multiply \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 13:56

12 Answers 12

6
\$\begingroup\$

R, 73.2 * 81 bytes = 5929.2 73.2 * 70 bytes = 5124.4 73.2 * 64 bytes = 4684.8

Same method as below for finding most common letters, but two significant improvements suggested in comments by @pajonk:

  1. -11 bytes thanks to a much better approach to splitting and concatenating strings using utf8ToInt().
  2. -6 bytes pointing out chartr() can take a range of characters (e.g. "a-d" rather than "abcd").
\(x)chartr(intToUtf8(names(sort(-table(utf8ToInt(x))))),'a-z',x)

Attempt This Online!

The ATO link includes a footer with 10k random test cases.


Original approach:

R, 73.2 * 81 bytes = 5929.2

\(x,p=paste0,`^`=Reduce)chartr(p^names(sort(-table(strsplit(x,"")))),p^letters,x)

Attempt This Online!

-4 bytes thanks to noticing I was being very foolish by using paste0() twice on the same string

How?

Fun question. R code golfers will no doubt be aware of the advice:

If you see a challenge involving string manipulation, just press the back button and read the next question. strsplit is single handily responsible for ruining R golf

I boldly ignored this, but it was quickly clear that once you've invested all the bytes in splitting the string into a vector, you need a fairly low-scoring mapping of letters to emojis to come in under 10k. I note some of the answers have slightly lower average value of results than my 73.2 (around the 73.1 range). If anyone has a lot of time and compute power, it would be interesting to see how low you could get it by brute forcing the best random seed.

The actual algorithm is not rocket science: simply assigning the most frequent character a, the next most frequent b etc. Here is an ungolfed version:

f <- function(x, p = paste0, `^` = Reduce) {
    s <- strsplit(x, "")
    m <- sort(-table(s))

    # Ungolfed equivalent to
    # chartr(
    #     Reduce(paste0, names(m)),
    #     Reduce(paste0, letters), x
    # )
    chartr(p^p^names(m), p^p^letters, x)
}

The main golfing is using this trick by @J.Doe:

Operator abuse gives you p=paste0;"^"=Reduce;p^p^r which saves two bytes on the usual paste0 call.

It also uses chartr(), basically the equivalent of Python's string translate() method, for an (irrelevantly) fast one-to-one replacement of each emoji with the letters of the alphabet. For clarity, the second argument should be letters[1:10], but chartr() is fine with the replacement being longer than the pattern to be replaced.


Approaches which aim to optimise the number of bytes:

R, 57 bytes * 90.2775 = 5145.8175

\(x)chartr(gsub("(.)(?=.*\\1)","",x,pe=T),"jihgfedcba",x)

Attempt This Online!

+7 bytes but still lower scoring than the previous approach below, thanks to the observation in the comment by Dominic Van Essen that, as the regex leads to assigning "a" to the emoji whose final appearance occurs first, you're more-likely to assign "a" to emojis that only appear once.

So if we reverse the replacement string to "jihgfedcba" then we end up with a value of 90.3, much lower than 116.9 in the approach below, which despite the increase in bytes leads to a better overall score. Although still not as low as the first method, and neither scores as well as the excellent answer by Dominic Van Essen.


Previous iteration

R, 116.935 * 57 bytes = 6665.295 116.935 * 50 bytes = 5846.75

Again -7 thanks to @pajonk as "abcdefghij" became "a-j" in chartr()

\(x)chartr(gsub("(.)(?=.*\\1)","",x,pe=T),"a-j",x)

Attempt This Online!

How?

This was an attempt to do it in as few bytes as possible. Rather than splitting the input string into a vector, this just finds the unique characters with regex, and replace them in the order they appear with the letters "a" to "j".

Nice to not have to strsplit() and paste() for once, and be able to do everything in chartr(). It's quite a bit shorter. But it's obviously not an optimal approach to the value part of the score, and the overall score is still higher than the original answer.

\$\endgroup\$
14
  • 1
    \$\begingroup\$ damn, this is really cool! I've never really used that much R myself. But the thing about random seed is that the score accounts for more than that. like the 2nd solution of Neil which has a horrendous average value of 287 but because it's only 6 bytes is still the 2nd best solution. So it is kind of more about figuring out a sweet spot. \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 23:07
  • 1
    \$\begingroup\$ Alternative 81 bytes \$\endgroup\$
    – pajonk
    Aug 21, 2023 at 7:33
  • 1
    \$\begingroup\$ Ah, right - for some reason I thought it won't work. No, you can use it if you like - I wouldn't have taken up the challenge without your post. \$\endgroup\$
    – pajonk
    Aug 21, 2023 at 8:15
  • 1
    \$\begingroup\$ Some byte-saves by remembering that chartr can take simple range of chars as input: \(x)chartr(intToUtf8(names(sort(-table(utf8ToInt(x))))),'a-z',x) (64), \(x)chartr(Reduce(paste0,names(sort(-table(strsplit(x,""))))),'a-z',x) (70), \(x)chartr(gsub("(.)(?=.*\\1)","",x,pe=T),"a-j",x) (50) \$\endgroup\$
    – pajonk
    Aug 21, 2023 at 11:27
  • 1
    \$\begingroup\$ By assigning in order of last appearance, you're more-likely to assign a to emojis that only appear once, since those that appear several times will likely have a last appearance that is later. As a result, multiply-appearing emojis will be biased to receive higher-valued letters. I think... \$\endgroup\$ Aug 22, 2023 at 15:34
4
\$\begingroup\$

05AB1E, score: 516.3704 (73.7672 * 7 bytes)

ÙΣ’}RA‑

Try it online or verify the average score of 10,000 random runs.

Explanation:

Γ™        # Uniquify the emoticon-characters of the (implicit) input-string
 Ξ£       # Sort it from lowest to highest by:
  Β’      #  The count of the character in the (implicit) input-string
   }R    # After the sort-by: reverse it from highest to lowest count
     A   # Push the lowercase alphabet string
      ‑  # Transliterate the descending sorted unique emoticons to lowercase letters in
         # the (implicit) input-string
         # (after which the result is output implicitly)
\$\endgroup\$
4
\$\begingroup\$

Charcoal, score 90.24 * 13 bytes = 1173.12

β­†ΞΈΒ§Ξ²βŒ•Ξ¦ΞΈβΌΞΌβŒ•ΞΈΞ»ΞΉ

Try it online! Link is to verbose version of code. Attempt This Online! Link is to test suite for 10000 random iterations. Explanation: Outputs the letter corresponding to the first time this emoji was found in the input. Note that the code is too slow for the test suite so it had to be optimised slightly.

Charcoal, score 287 * 6 bytes = 1722

⭆θ§β℅ι

Try it online! Link is to verbose version of code. Attempt This Online! Link is to test suite for 10000 random iterations. Explanation: Outputs the letter corresponding to the emoji's ordinal modulo 26.

Charcoal, score 73.275 * 25 bytes = 1831.875

W⁻βͺͺΞΈΒΉΟ…βŠžΟ…βŠŸβŒˆοΌ₯ΞΉβŸ¦β„–ΞΉΞΊΞΊβŸ§β­†ΞΈΒ§Ξ²βŒ•Ο…ΞΉ

Try it online! Link is to verbose version of code. Attempt This Online! Link is to test suite for 10000 random iterations. Explanation:

W⁻βͺͺΞΈΒΉΟ…βŠžΟ…βŠŸβŒˆοΌ₯ΞΉβŸ¦β„–ΞΉΞΊΞΊβŸ§

Sort the characters by frequency in descending order.

β­†ΞΈΒ§Ξ²βŒ•Ο…ΞΉ

Map them to lowercase letters.

\$\endgroup\$
2
  • \$\begingroup\$ The modulo attempt is nifty! Drastically reducing programming size at the cost of a slightly higher average value pays off pretty well. I wonder if a programming language exists in which the algorithm could be implemented in only 1 or 2 bytes, which would put the program's score in the vicinity of the current bests, despite a 3x average value. \$\endgroup\$ Aug 22, 2023 at 14:23
  • 1
    \$\begingroup\$ @MatthieuM. Best I could do in 05AB1E is 4 bytes: ÇAsè, so its score would be worse. \$\endgroup\$
    – Neil
    Aug 22, 2023 at 18:18
4
\$\begingroup\$

R, 112.5 * 34 bytes = 3825

\(x)letters[utf8ToInt(x)%%430%%11]

Attempt This Online!

Output is a vector of characters in the range "a" to "j".

Developed independently of, but uses similar approach to Arnauld's Javascript answer.

The aim here is to maximally golf the code-size, while keeping the encrypted letters in the range a-j (but ignoring their abundances in the input).
We find the modulo values to map the emoji character codes to 1:10 using:

x=utf8ToInt("πŸ—“πŸ™πŸ“—πŸ’»πŸ“‡πŸš’πŸ‰πŸ’˜πŸ˜ͺπŸŽ‰")
for(i in 1:max(x))for(j in 1:99){y=unique(x%%i%%j);if(min(y)==1&&max(y)==10&&length(y)==10)print(c(i,j))}

which pretty quickly outputs 430 11 and nothing else.


R, 112.5 * 39 bytes = 4387.5

\(x)intToUtf8(utf8ToInt(x)%%430%%11+96)

Attempt This Online!

As above, but outputs as a single string (same as SamR's R answer).


R, 73.2 * 61 bytes = 4465.2

\(x)intToUtf8(rank(-table(y<-el(strsplit(x,""))),,"f")[y]+96)

Attempt This Online!

Minimizing the output string value at the expense of code-size doesn't seem to pay-off.

\$\endgroup\$
3
\$\begingroup\$

JavaScript (ES5), score: 112.346 * 62 bytes = 6965.433

x=>x.replace(/../g,e=>(e.charCodeAt(1)%576%11+9).toString(36))

Attempt This Online! A simple hashing method; optimized for the specific emoji specified in the challenge.

JavaScript (ES6), score: 73.152 * 109 bytes = 7973.590

x=>x.replace(/./gu,e=>"abcdefghij"[[...new Set(x)].sort((a,b,c=e=>x.split(e).length)=>c(b)-c(a)).indexOf(e)])

Attempt This Online! A more generic approach, which sorts the input's unique characters (code points) to minimize the value per run.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ Welcome to Code Golf, and nice answer! \$\endgroup\$ Aug 21, 2023 at 15:59
  • \$\begingroup\$ @RydwolfPrograms thank you! \$\endgroup\$ Aug 22, 2023 at 12:53
3
\$\begingroup\$

Ruby, 90.0438 * 31 bytes = 2,791.3578

->s{s.tr s.chars.uniq*'','a-z'}

Attempt This Online!

Ruby, 73.1206 * 53 bytes = 3,875.3918

->s{s.tr s.chars.uniq.sort_by{-s.count(_1)}*'','a-z'}

Attempt This Online!

\$\endgroup\$
2
\$\begingroup\$

Python, score 103.6078*101 bytes = 10464,3878

assigns the characters in alphabetical order

lambda l:''.join(chr(97+[x for(x,_)in groupby(sorted(l))].index(c))for c in l)
from itertools import*

Attempt This Online!


Python, score 73.2 * 136 bytes = 9955,2

-21 bytes, thanks to @Value Ink

always produces a string with the lowest possible average byte value

lambda l:''.join(chr(97+[x for(_,x)in sorted([(-len([*n]),c)for(c,n)in groupby(sorted(l))])].index(c))for c in l)
from itertools import*

Attempt This Online!

\$\endgroup\$
6
  • \$\begingroup\$ Theres an error in the output (check 9th character) it should be b not e \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 13:53
  • \$\begingroup\$ The character ⭕️ in the input consist of two Unicode code-points ('β­•' 11093 and '️' 65039), handling this case will be difficult, I suggest replacing that character with a single code-point character \$\endgroup\$
    – bsoelch
    Aug 20, 2023 at 14:04
  • \$\begingroup\$ oh Thanks! I'll change it to 🚒 \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 14:07
  • \$\begingroup\$ It seems like β­• is a single character but for some reason the string contained a "variant-selector character" after it. \$\endgroup\$
    – bsoelch
    Aug 20, 2023 at 14:08
  • \$\begingroup\$ after changing that your code works perfectly! with the score 12435.1379 \$\endgroup\$
    – 3.14
    Aug 20, 2023 at 14:09
2
\$\begingroup\$

sclin, 89.8656 * 30 bytes = 2695.968

dup""uniq $abc","zip >M swap :

Try it here! Uses a very simple index-based mapping.

For testing purposes:

10000=$n.
"πŸ—“πŸ™πŸ“—πŸ’»πŸ“‡πŸš’πŸ‰πŸ’˜πŸ˜ͺπŸŽ‰"=$e.
$W ( ; S>c 97- +/ ) map $n tk +/ $n / n>o
$W ( $e "."/?& :r ) map 25tk dup >o " => ">o ; dup n>o
dup""uniq $abc","zip >M swap :

Explanation

Prettified code:

dup () uniq $abc \, zip >M swap :
  • dup () uniq get unique emojis
  • $abc \, zip pair emoji with alphabet to create translation map
  • swap : translate emojis to alphabet

sclin, 73.147 * 50 bytes = 3657.35

""Q""group"len _"sort $abc"0.: dip ,"zip >M swap :

Try it here! Uses frequency-based mapping.

For testing purposes:

10000=$n.
"πŸ—“πŸ™πŸ“—πŸ’»πŸ“‡πŸš’πŸ‰πŸ’˜πŸ˜ͺπŸŽ‰"=$e.
$W ( ; S>c 97- +/ ) map $n tk +/ $n / n>o
$W ( $e "."/?& :r ) map 25tk dup >o " => ">o ; dup n>o
""Q""group"len _"sort $abc"0.: dip ,"zip >M swap :

Explanation

Prettified code:

dup () group ( len _ ) sort $abc ( 0.: dip , ) zip >M swap :
  • () group group emojis
  • ( len _ ) sort sort descending by frequency
  • $abc ( 0.: dip , ) zip >M pair emoji with alphabet to create translation map
  • swap : translate emojis to alphabet
\$\endgroup\$
2
\$\begingroup\$

Python, 107.3917 * 38 bytes = 4080.8846

lambda x:[97+ord(c)%3203%13for c in x]

Attempt This Online!

Outputs as a list of codepoints. Scoring script taken from bsoelch's answer.

Python, 101.9159 * 61 bytes = 6216.870

lambda x:x.translate(dict(zip(map(ord,set(x)),"abcdefghij")))

Attempt This Online!

Outputs as a string.

\$\endgroup\$
1
\$\begingroup\$

JavaScript (ES6), 112.5 * 24 bytes = 2700

Expects and returns a list of code points.

a=>a.map(n=>n%430%11+96)

Try it online!

This version uses a fixed mapping of the smileys to abcdefghij.

The theoretical average score is:

$$\frac{25}{10}\sum_{n=0}^9 n=112.5$$


JavaScript (Node.js), ~90 * 50 bytes = ~4500

s=>s.replace(e=/../g,c=>Buffer([e[c]||=i++]),i=97)

Attempt This Online!

This version uses a dynamic mapping of the smileys to abcdefghij, according to their order of appearance.

\$\endgroup\$
1
\$\begingroup\$

Excel, 89.9 * 67 bytes = 6022.9

=LET(c,MID(A2,ROW(1:25)*2-1,2),CONCAT(CHAR(96+MATCH(c,UNIQUE(c),0))))

This does not optimize for the lowest character sum because doing so in Excel takes so many bytes that the overall score ends up higher. The formula is written for the input (the 25 random emojis) to be in the cell A2. To simulate 10,000 runs, I wrote a formula to generate those random string, copied it down for 10,000 rows, then copy / pasted values. The encrypted results are on PasteBin and the end result in Excel look like this:

Screenshot

Note: When I copy / pasted down the formula from B2, I changed the row reference to be absolute ($1:$25) so it would work on all the rows below the original formula.


Explanation:

  • LET(c,MID(A2,ROW(1:25)*2-1,2) defines the variable c to be an array with one emoji per row. It's a little tricky to pull out only because Excel sees these emojis as being 2 characters long so need to pull out strings of length 2.
  • CONCAT(CHAR(96+MATCH(c,UNIQUE(c),0)))
    • UNIQUE(c) filters for just the unique emojis
    • MATCH(c,UNIQUE(c),0) finds the first instance of each emoji within that unique list
    • CHAR(96+MATCH(~)) converts that match value into a character in the range a-j
    • CONCAT(CHAR(~)) combines all those characters into one string
\$\endgroup\$
1
\$\begingroup\$

Haskell, score 73.2 * 110 bytes = 8052.0

import Data.List
j(Just x)=x
f s=map(toEnum.(97+).j.(`elemIndex`(map head$sortOn((0-).length)$group$sort s)))s

Haskell, score 90.1 * 72 bytes = 6487.2

import Data.List
j(Just x)=x
f s=map(toEnum.(97+).j.(`elemIndex`nub s))s
\$\endgroup\$

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.