# Most efficient cubifier

Cubically is too tedious to manually write any code in. Your challenge is to translate ASCII text into Cubically source code.

# Cubically

This is just a quick run-down of Cubically; the repository has a more complete guide and details.

Cubically is an esolang I wrote a while ago, designed to be painful to use. It contains two pieces of memory, a 3x3x3 Rubik's Cube and a register called the "notepad".

### Memory

The internal Rubik's Cube is initialized like this:

   000
000          top face
000
111222333444    left, front, right, and back faces, respectively
111222333444
111222333444
555
555          down face
555


After performing a clockwise 90° turn on the right face, the memory cube would look like this:

   002
002
002
111225333044
111225333044
111225333044
554
554
554


### Commands

A non-integer character sets the default command. For each integer before the default command is set once again, the command is performed with that integer. For example, x524y312 would perform command x with 5, then with 2, then with 4, then perform command y with 3, then with 1, then with 2.

The integers that commands use represent face indexes. So x0 would perform x on the UP (0-indexed) face. x1 would perform x on the LEFT (1-indexed) face, and so on.

Performing any command with 6 will perform that command on the notepad value. Performing any command with any integer over 6 will result in an error.

Here are some example commands:

• R1 - turn the RIGHT face clockwise 90° so the internal cube will look like the second example above
• R11 - turn the RIGHT face clockwise 90° twice, identical to R2
• +0 - add all values of the UP face to the notepad
• +000 - add all values of the UP face to the notepad three times
• @6 - print the nonexistent 6th-indexed face (memory) as a character
• %4 - print the sum of all values on the BACK face as an integer

A complete list of commands and syntax is available at the repository.

# Challenge

You will take ASCII text as input and print a Cubically program as output.

Examples (stolen from here and here):

Input -> Output
Hello, World! -> +53@6+1F2L2+0@6L2F2U3R3F1L1+2@66L3F3R1U1B3+0@6:4U1R1+00@6-000@6*0-4+000@6-00@6+2-000000@6-5+4000@6-00@6/0+00@6:0+0/0+00@6
1$2$3$4$5$6$7$8$9$10$ -> B1+2/2%6@4+00/0%6@4+00/1%6@4+21/1%6@4+30/0%6@4+22/1%6@4+22/1%6@4+40/1%6@4+52/1%6@4+42/1%6@4


# Rules

• Your program may not contain a dictionary containing the translations for the 100 testcases.
• Your program must finish in less than 180 seconds (no brute-force programs that take weeks).
• Your program must output valid Cubically code that finishes in less than 180 seconds.
• Your program will take input via standard input, unless you want to mess with the test driver.
• Your program must output Cubically code that produces nothing but your program's input when run. ಠ_ಠ

# Scoring

You will test your program with 100 pseudorandom strings of pseudorandom length. (A bash script is provided that will do this for you.) Here is how you will score:

• Let the length of the output program be o.
• Let the length of the input string be l.
• Let a variable r be the result of o/l.
• Find the average of all r: (r1 + r2 + r... + r100) / 100.

Test with this script. You'll have to modify it as instructed. Note that the program does not check whether the output is valid Cubically code. If you can't get the script working, I can help. Ping me in the Cubically chat room.

• Sandbox post – MD XF Jul 21 '17 at 17:07
• Would "@6 - print the sum of the nonexistent 6th-indexed face (notepad) as a character" be more accurate? Is %4 also a sum? Are + commands sum face then add that to all values or...? – Jonathan Allan Jul 21 '17 at 23:58
• @JonathanAllan @6/%6 just directly prints the notepad value as a character/integer. @x/%x (where x is any existing face) adds all values on the x-indexed face and prints the sum as a character/integer. + adds all the values on the specified face to the register. – MD XF Jul 22 '17 at 1:38
• Ah, for some reason I was thinking of the notepad as having 9 values too. – Jonathan Allan Jul 22 '17 at 1:58

# C++11, Score: 6.37

#include <iostream>
#include <vector>
#include <array>
#include <limits>
#include <algorithm>

const int inf = std::numeric_limits<int>::max(),
maxDiff = 128, nFace = 6;
std::array<int, maxDiff+1> plusvalue, totalvalue, plustrace, totaltrace;
std::array<int, nFace> input;

void prtrace(int value) {
while (value) {
std::cout << plustrace[value];
value -= input[plustrace[value]];
}
}

void prexpr(int i) {
char xorwt = 0;
if (i < 0) {
xorwt = '+' ^ '-';
i = -i;
}
if (totalvalue[i] != 0 && totalvalue[i] != inf) {
std::cout << (char)('+' xor xorwt);
prtrace(totaltrace[i]);
if (totaltrace[i] != i) {
std::cout << (char)('-' xor xorwt);
prtrace(totaltrace[i] - i);
}
}
}

int main() {
std::cout << "RU";
input = {6, 15, 27, 26, 19, 42};
std::cin >> std::noskipws;

std::fill(plusvalue.begin(), plusvalue.end(), inf);
plusvalue[0] = 1; // '+'
for (int i = 0; i < nFace; ++i) { // knapsack, each value repeated inf times
int val = input[i];
if (val == 0) continue;
for (int p = 0; p <= maxDiff - val; ++p) {
if (plusvalue[p] != inf && plusvalue[p + val] > plusvalue[p] + 1) {
plusvalue[p + val] = plusvalue[p] + 1;
plustrace[p + val] = i;
}
}
}
for (int p = 0; p <= maxDiff; ++p) totalvalue[p] = plusvalue[p], totaltrace[p] = p;
totalvalue[0] = 0;
for (int sub = 1; sub <= maxDiff; ++sub) {
if (plusvalue[sub] == inf) continue;
for (int p = 0; p <= maxDiff - sub; ++p) {
if (plusvalue[p+sub] != inf && totalvalue[p] > plusvalue[p+sub] + plusvalue[sub]) { // include '+'s
totalvalue[p] = plusvalue[p+sub] + plusvalue[sub];
totaltrace[p] = p+sub;
}
}
}

//    // Note: plustrace[x] = i<=nFace : plustrace[x-input[i]] + 1 = plustrace[x]
//    long long sum = 0;
//    for (int i = 0; i <= maxDiff; ++i) {
//        sum += totalvalue[i];
//        std::cout << i << '\t' << totalvalue[i] << '\t';
//        prexpr(i);
//        std::cout << '\n';
//    }
//
//    std::cout << "_______________________________\n\nAverage = " << sum / (maxDiff + 1.) << '\n';

// RU 3.98131

char ch;
int cur = 0;
while (std::cin >> ch) {
int diff = ch - cur;
prexpr(diff);
std::cout << "@6";
cur += diff; // cur = ch
}
}

/*
RU 3.98131
*/


Try it online! (generate Cubically code from ASCII) and (run Cubically code)

Explanation:

• First the program prints "RU", which make the face sum from {0,9,18,27,36,45} to {6, 15, 27, 26, 19, 42}. What makes that face sum set useful is that the gcd is 1, so by Bézout's identity there exist a way to construct any number d from a sum (or difference) of those numbers.
• Therefore, if the next char is ch and the current notepad value is n, then let d = ch - n, we can execute Cubically commands in the form +{digits from 0 to 5}-{digits from 0 to 5} such that the notepad value becomes ch. Then just execute %6 to print notepad value.
• To find the most efficient way to express d as a sum/difference of numbers in the face sum set, I use Knapsack algorithm for all numbers from 0 to 128. E.g., for d=1, the program gets 27 - 26 = 1, so it prints +2-3, which is 27 - 26 = 1. Which can be seen when run the program with input abc, the program output

RU+4333@6+2-3@6+2-3@6

• Woah, great job! The Knapsack algorithm is what we were looking for I guess. – TehPers Sep 8 '17 at 17:08
• Note that due to language updates, you can get a better score via calling @ implicitly - @6 can be shortened to @ in all cases. – MD XF Nov 26 '17 at 5:29

# Lua, Score: 85.9113.5013.2012.709.419.329.839.669.129.06 8.03 (Average)

-- Get input

local out = ""
local faces = { [5] = 45, [4] = 36, [3] = 27, [2] = 18, [1] = 9 }
local lastChar = nil

-- Mode the program is in
-- 2 = not set (needs :), 1 = just set (needs +), 0 = normal
local mode = 1;
for i = 1, inp:len() do
-- Character value at current position
local c = string.byte(inp, i)

if c == lastChar then
-- Repeat character
out = out .. "6"
elseif c % 9 == 0 and c <= 45 then
if #out == 0 then
out = out .. "@"
end
out = out .. (c / 9)
else
local c2 = c

-- Handle if difference from lastChar is divisible by 9
if lastChar and (c - lastChar) % 9 == 0 then
local difference = c - lastChar
if difference > 0 then
out = out .. "+"
else
out = out .. "-"
difference = difference * -1
end

for index = 5, 1, -1 do
local face = faces[index]
while difference >= face do
difference = difference - face
out = out .. index
end
end
c = 0
end

-- Handle anything not divisible by 9
local extra = c % 9
if extra > 0 then
-- Try to optimize by dividing by 9, if possible
if lastChar and math.floor(lastChar / 9) == extra then
out = out .. "/1"
mode = 1
extra = 0
else
while extra > 0 do
local n = extra > 5 and 5 or extra

if mode == 2 then
out = out .. ":"
mode = 1
elseif mode == 1 then
out = out .. "+"
mode = 0
end
out = out .. n
extra = extra - n
end
out = out .. "/1"
mode = 1
end

c = c - (c % 9)
end

-- Handle anything divisible by 9
for index = 5, 1, -1 do
local face = faces[index]
while c >= face do
if mode == 2 then
out = out .. ":"
mode = 1
elseif mode == 1 then
out = out .. "+"
mode = 0
end
c = c - face
out = out .. index
end
end

out = out .. "@6"
lastChar = c2
end

mode = 2
end
print(out)


Try it online!

Okay, I don't think I can optimize this anymore.

This version iterates through each character, adding c % 9 (where c is the character's decimal value) by doing :5+2/1, then adds the parts divisible by 9 by adding that face's value. For example: :2/1+551@ to print "e", where :2/1 adds 2, +551 adds 99 (9 * (5 + 5 + 1), or 9 * 11), and @ prints the output. Input is read with io.read().

Optimizations include directly adding/subtracting after printing if the difference between characters is a multiple of 9, dividing the current value if possible rather than setting c % 9 from scratch, and repeating characters by printing the current value again rather than recalculating it. Additionally, I've implemented Kamil's method of instantly printing any face that already contains the target value, and MD XF's suggestion to not use : at the beginning, but instead just start with a +.

• You can always comment on your own questions and answers, but you're not quite to general comment privileges yet. Shouldn't be long with answers like this (or, more likely, just this answer once a few more people see it) – Kamil Drakari Jul 25 '17 at 22:46
• @MDXF I'm not quite that nice :P – Kamil Drakari Jul 26 '17 at 13:18
• You can change local inp = io.read() to local inp = io.read("*all"). That fixes the problem. – MD XF Jul 26 '17 at 16:32
• Another possible optimization - since the notepad starts at 0, you don't actually need to output, e.g. :5+124, instead you can just write +5124, which will probably get the score down a little bit if you tweak it right. – MD XF Jul 29 '17 at 18:43
• You'll likely get a better score if you change your answer to support some of the recent Cubically updates, such as implicit face turns. – MD XF Aug 16 '17 at 19:46

# Cubically, Score: 86.98

U3D1R3L1F3B1U1D3~:7+1(-1@3(-1%1)6:1+3111@6%1-31111+004@6:1+11111%6:1+45@6:1-1%6~:7+1)6


Try it online!

Turns out, all you need is conditional loops, a face equal to 1, and consistent End-Of-Input behavior.

U3D1R3L1F3B1U1D3     set LEFT face sum to 1

(                    open loop that can always be jumped to
@3                   print RIGHT face (ASCII 43 '+')

##   the following mechanism gets the output program's   ##
##   notepad to the current inputted ASCII value:        ##

(                    open loop that can always be jumped to
%1                   print '1'
)6                   jump back to loop while notepad is nonzero

##   the following mechanism prints "/1@6:1"             ##

:1+3111@6            set notepad to 47,    print (ASCII 47 '/')
%1                   print LEFT face       print (integer '1')
-31111+004@6         set notepad to 64,    print (ASCII 64 '@')
:1+11111%6           set notepad to 6,     print (integer 6)
:1+45@6              set notepad to 58,    print (ASCII 58 ':')
:1-1%6               set notepad to 0,     print (integer 1)

:7+1                 set notepad to input plus 1, so EOF changes to zero
)6                    loop if notepad is truthy


The adding/subtracting of the LEFT face is to get the loop to end when EOF is read.

• You have got to be kidding. This is incredible. – MD XF Aug 5 '17 at 3:31
• Oh hey, it even has a better score than my original C# answer! – Kamil Drakari Aug 5 '17 at 3:51
• Note that due to language updates, you can get a better score via calling @ implicitly - @6 can be shortened to @ in all cases. – MD XF Nov 26 '17 at 5:29

# C# (.NET Core), Score: 129.9811.7310.829.6210.3310.32 10.20

-1.2 point from MD XF's suggestion to use @6666... instead of @6@6@6@6... for repeating character, and superior initialization sequence

static void Main()
{
List<byte> input = new List<byte>();
while (inChar != -1)
{
}

Console.Write("U3D1R3L1F3B1U1D3");
byte[] sides = new byte[] { 20, 1, 14, 43, 24, 33 };

byte currentChar = 0;

if(currentChar == input[0] || sides.Contains(input[0])) Console.Write("@");

foreach (byte character in input)
{
if (currentChar == character)
{
Console.Write("6");
continue;
}

if (sides.Contains(character))
{
Console.Write(Array.IndexOf(sides, character));
continue;
}
if (currentChar < character)
{
Console.Write("+");
while (currentChar < character)
{
byte nextAdd = sides.Where(s => s + currentChar <= character).Max();
}
}

if (currentChar > character)
{
Console.Write("-");
while (currentChar > character)
{
byte nextSub = sides.Where(v => currentChar - v >= character).Max();
currentChar = (byte)(currentChar - nextSub);
Console.Write(Array.IndexOf(sides, nextSub));
}
}

Console.Write("@6");
}
}


Try it online!

My newest version actually does some manipulation of the cube! Yay!

That first Console.Write up there is a fixed manipulation MD XF worked out that creates this cube:

   242
202
242
000131555313
010121535343
000131555313
424
454
424


The significance of this cube is that one of its sides has a sum of 1, allowing manipulations of the Notepad on a smaller scale than multiples of nine, and in particular it simplifies relative movement rather than needing to start from zero each character; in this algorithm both addition and subtraction are used to take the shortest path between characters.

MD XF's version of the initialization causes side 2 to have a sum of 14, which saves many bytes of output for ASCII distances between 14 and 20.

Now can handle inputs with internal newlines, Console.Read() gets individual characters until End of File; see the TIO link which should have the input

Hello,
World!


Shaved a couple fractions of a point by immediately outputting a character if its ASCII value just happens to already exist on a side.

Test Script courtesy of MDXF

Previous submission here and explanation:

This is kinda boring, but as far as I can tell it works. Admittedly I only tried Hello, World! but I ran the output in the TIO Cubically interpreter and it output "Hello, World!" so I assumed it works.

Rather than actually manipulating the cube at all, the notepad is simply incremented by the sum of the 1 face (9) repeatedly until it has the right value for each character, then prints it.

• Comments are not for extended discussion; this conversation has been moved to chat. – Martin Ender Sep 3 '17 at 13:03
• @MartinEnder Could you move them to the existing chatroom instead? – MD XF Sep 5 '17 at 19:46
• @MDXF I could but I can't tell whether they'd end up being completely out of place and context in that chatroom. – Martin Ender Sep 5 '17 at 20:27
• @MartinEnder The comments are older than the chatroom, so they would just appear way back in the transcript, correct? – MD XF Sep 5 '17 at 20:28
• They would. Thanks, I'll move the messages. – Martin Ender Sep 5 '17 at 20:28