Most efficient cubifier

19

1

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.

MD XF

Posted 2017-07-21T17:07:14.213

Reputation: 11 605

1Sandbox post – MD XF – 2017-07-21T17:07:51.923

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 – 2017-07-21T23:58:25.353

@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 – 2017-07-22T01:38:31.023

Ah, for some reason I was thinking of the notepad as having 9 values too. – Jonathan Allan – 2017-07-22T01:58:36.950

Answers

4

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

user202729

Posted 2017-07-21T17:07:14.213

Reputation: 14 620

Woah, great job! The Knapsack algorithm is what we were looking for I guess. – TehPers – 2017-09-08T17:08:16.207

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 – 2017-11-26T05:29:08.297

17

Lua, Score: 85.91 13.50 13.20 12.70 9.41 9.32 9.83 9.66 9.12 9.06 8.03 (Average)

-- Get input
local inp = io.read("*a")

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 +.

TehPers

Posted 2017-07-21T17:07:14.213

Reputation: 899

I actually feel kinda bad for posting a few minutes faster than this much better answer... – Kamil Drakari – 2017-07-25T22:40:08.670

@KamilDrakari Oh hey, I can comment now! No worries. Your answer is much cleaner than mine anyway. – TehPers – 2017-07-25T22:42:28.130

1You 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 – 2017-07-25T22:46:34.967

@KamilDrakari Oh I see, thanks. – TehPers – 2017-07-25T22:49:40.297

Good job to both of you, and great first answer, TehPers! @KamilDrakari, if you'd like to request that Magic Octopus Urn awards the bounty to this answer instead, I can let him know. (It's your choice; you won the bounty) – MD XF – 2017-07-26T03:34:04.850

2@MDXF I'm not quite that nice :P – Kamil Drakari – 2017-07-26T13:18:14.123

Holy wow, you're still improving this?!? – MD XF – 2017-07-26T16:09:00.277

@MDXF I noticed that the "c % 9" bit could be shortened. Regardless, Kamil's new answer is a lot more fun since it actually rotates the faces. – TehPers – 2017-07-26T16:14:46.020

Something you can optimize is printing more than one of the same character. For example, @6@6@6@6@6 to @66666 – MD XF – 2017-07-26T16:20:11.470

@MDXF Oh, good idea! I'll add that now real quickly – TehPers – 2017-07-26T16:21:11.553

Also, this doesn't work for multiple-line inputs. For example, with the input This is a test<newline>Tests are the best, your code will output this.

– MD XF – 2017-07-26T16:23:28.353

@MDXF The main issue is that my two options are io.read(), which reads whatever happens to be input (generally by the user), and f = io.open("str.txt") inp = f:read("*a") f:close(), which only works because you save to a text file, but also gives an extra newline of input (which does work). I can switch back to reading from the filesystem if you want. It only prints the first line for you because it seems to provide input twice, once per line, for that test. – TehPers – 2017-07-26T16:27:11.937

1You can change local inp = io.read() to local inp = io.read("*all"). That fixes the problem. – MD XF – 2017-07-26T16:32:02.547

1Another 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 – 2017-07-29T18:43:44.310

1You'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 – 2017-08-16T19:46:04.667

1Note that due to language updates, you can get a better score via calling @ implicitly - @6 can be shortened to @ in all cases. – MD XF – 2017-11-26T05:29:16.190

It appears this is broken right now; it often prints 5.0 in seemingly random places, and the output is wrong. I can't paste the TIO link here as it is too long, but I'll send it in the Cubically chatroom.

– MD XF – 2017-12-07T19:09:40.553

16

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
~:7                  read input, set notepad to input
+1                   add 1 to notepad

(                    open loop that can always be jumped to
 -1                   subtract 1 from notepad
 @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                   subtract 1 from notepad
  %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)

 ~                    read input
 :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.

Kamil Drakari

Posted 2017-07-21T17:07:14.213

Reputation: 3 461

2You have got to be kidding. This is incredible. – MD XF – 2017-08-05T03:31:59.817

Oh hey, it even has a better score than my original C# answer! – Kamil Drakari – 2017-08-05T03:51:22.317

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 – 2017-11-26T05:29:26.673

9

C# (.NET Core), Score: 129.98 11.73 10.82 9.62 10.33 10.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>();
            int inChar = Console.Read();
            while (inChar != -1)
            {
                input.Add((byte)inChar);
                inChar = Console.Read();
            }


            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();
                        currentChar = (byte)(currentChar + nextAdd);
                        Console.Write(Array.IndexOf(sides, nextAdd));
                    }
                }

                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.

Kamil Drakari

Posted 2017-07-21T17:07:14.213

Reputation: 3 461

Comments are not for extended discussion; this conversation has been moved to chat.

– Martin Ender – 2017-09-03T13:03:02.183

@MartinEnder Could you move them to the existing chatroom instead?

– MD XF – 2017-09-05T19:46:51.103

@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 – 2017-09-05T20:27:01.797

@MartinEnder The comments are older than the chatroom, so they would just appear way back in the transcript, correct? – MD XF – 2017-09-05T20:28:11.020

They would. Thanks, I'll move the messages. – Martin Ender – 2017-09-05T20:28:58.490

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 – 2017-11-26T05:29:38.813