11
\$\begingroup\$

In this task, you will write a program/function that takes a Normalized Malbolge program and outputs the resulting Malbolge program. (This is a secret tool that all Malbolge programmers are using!)

Input

A data structure that (somehow) represents a Normalized Malbolge program.

Output

A data structure that represents the resulting Malbolge program.

Examples

jpoo*pjoooop*ojoopoo*ojoooooppjoivvvo/i<ivivi<vvvvvvvvvvvvvoji
(=BA#9"=<;:3y7x54-21q/p-,+*)"!h%B0/.~P<<:(8&66#"!~}|{zyxwvugJ%

jjjj*<jjjj*<v
('&%#^"!~}{XE

jjjjjjjjjjjjjjjjjjjjjjj*<jjjjjjjjjjjjjjjjjjjjjjjj*<v
('&%$#"!~}|{zyxwvutsrqpnKmlkjihgfedcba`_^]\[ZYXWVT1|

How to convert

Iterate over the normalized Malbolge program, performing the following steps for each character:

  1. Replace the characters in the string *jpovi</ with the corresponding character in '(>DQbcu. (That is, map * to ', j to (, and so on.)

  2. Then subtract the current position of the program counter (i.e. the number of characters before the current one) from the character's ASCII code.

  3. If the resulting ASCII code is less than 33, increment it by 94 and repeat until it is at least 33.

  4. Append the resulting character to the output.

Rules

  • This is a contest; the shortest answer wins.
  • No standard loopholes please.
  • The default I/O methods are allowed.
  • Input will only contain the characters *jpovi</.
\$\endgroup\$
  • 4
    \$\begingroup\$ Does the input contain only characters from "*jpovi</"? \$\endgroup\$ – Joel Aug 28 at 4:02
  • 7
    \$\begingroup\$ I don't understand what "Then, minus the position." means. I probably could figure it out from the pseudocode, but the explanation should be self-contained. \$\endgroup\$ – xnor Aug 28 at 4:41
  • 1
    \$\begingroup\$ "While the temporary Malbolge representations' ASCII code is less than 33, increment the char by the 94." What do you mean by this? How I understood the challenge is: 1) map characters; 2) convert to unicode value; 3) decrease each by program counter (we now have what you call the 'temporary Malbolge representations' ASCII code'); 4) if any value is less than 33, increment that by 94; 5) convert these values back to characters. But using this approach the output clearly isn't correct.. So could you clarify what you mean by this for inputs longer than 33 characters? \$\endgroup\$ – Kevin Cruijssen Aug 28 at 9:44
  • 1
    \$\begingroup\$ a: if ascii_code(temporary Malbolge representation) < 33: char := char + 94; goto a; \$\endgroup\$ – A _ Aug 28 at 10:03
  • 1
    \$\begingroup\$ I'll happily give a bounty to anyone who can write one of these things in Malbolge :) \$\endgroup\$ – JDL Aug 29 at 13:16

13 Answers 13

4
\$\begingroup\$

Jelly, 29 22 bytes

Oị“%þV DCµ2®  ‘_JịØṖḊ¤

Try it online!

A monadic link taking a Jelly string as its argument and returning a Jelly string.

Thanks to @JonathanAllan for saving 2 bytes!

Explanation

O                      | Convert to Unicode code points
 ị“%þV DCµ2®  ‘        | Index into compressed integer list [37, 31, 86, 32, 68, 67, 9, 50, 8, 32, 32] (note the 32s are never actually used because the input mod 11 will be one of 1,2,3,5,6,7,8,9)
               _J      | Subtract position of character in original string
                 ị   ¤ | Index into the following as a nilad:
                  ØṖ   | - Printable ASCII characters
                    Ḋ  | - With the first character (space) removed
\$\endgroup\$
6
\$\begingroup\$

Python 3, 82 bytes

p=0
for c in input():print(end=chr((b"de{#0ABT"["*jpovi<".find(c)]-p)%94+33));p+=1

Try it online!

Thanks to @Joel for replacing the ugly unprintable characters in the bytestring with printable ones.

I'm looking for a mod-chain to replace "*jpovi<".find(c), but I don't think there is one that's shorter, and a non-exhaustive brute-force search hasn't found anything so far.

82 bytes

f=lambda s,p=0:s and chr((b"de{#0ABT"["*jpovi<".find(s[0])]-p)%94+33)+f(s[1:],p+1)

Try it online!

\$\endgroup\$
  • \$\begingroup\$ The unprintable ASCII characters can be offset by 94 to be made into printable ones for better readability. \$\endgroup\$ – Joel Aug 28 at 5:18
  • \$\begingroup\$ You could try to find a math function to replace the mapping b"de{#0ABT"["*jpovi<".find(c)] if you have a program for that. \$\endgroup\$ – Joel Aug 28 at 5:39
  • 1
    \$\begingroup\$ @Joel Unfortunately, I think that mapping is far too wide a search space for an arithmetic function, at least with the tools I have. I had just been searching modulo chains like x%84%15%7 for the right half of the mapping, but I think I can recycle some code I wrote for another challenge to search including * and / terms. \$\endgroup\$ – xnor Aug 28 at 5:44
  • \$\begingroup\$ @Joel I'm not finding anything mod-chain-style for the right side using % and * (// in Python 3 probably isn't worth it.) In fact, nothing has matched the first 6 of 7 values. I hoped this would work because a rough entropy estimate says there's probably enough expressions ending in %7`, but it's close. And maybe these chains give outputs that are far from evenly distributed, especially since once two inputs collapse to the same value, no further operations can separate them. The stuff I'm trying is still far too dumb to search the larger expression, but if you have any ideas, go for it. \$\endgroup\$ – xnor Aug 28 at 6:23
  • \$\begingroup\$ I think a better algorithm is probably needed for arbitrary inputs like map(ord, "*jpovi<"). If the output does not preserve the order for most of inputs (i.e. f(m)>=f(n) if m>=n), some carefully crafted constants for % and * are likely needed and a brute-force search is unlikely to yield a positive outcome. \$\endgroup\$ – Joel Aug 28 at 6:47
6
+250
\$\begingroup\$

Malbolge Unshackled (20-trit rotation variant), 7,784e6 bytes

Size of this answer exceeds maximum postable program size (eh), so the code is located in my GitHub repository.

How to run this?

This might be a tricky part, because naive Haskell interpreter will take ages upon ages to run this. TIO has decent Malbogle Unshackled interpreter, but sadly I won't be able to use it (limitations).

The best one I could find is the fixed 20-trit rotation width variant, that performs very well, converting 0,5 characters per second.

To make the interpreter a bit faster, I've removed all the checks from Matthias Lutter's Malbolge Unshackled interpreter.

My modified version can run around 6,3% faster.

#include <malloc.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char* translation = "5z]&gqtyfr$(we4{WP)H-Zn,[%\\3dL+Q;>U!pJS72Fh"
        "OA1CB6v^=I_0/8|jsb9m<.TVac`uY*MK'X~xDl}REokN:#?G\"i@";

typedef struct Word {
    unsigned int area;
    unsigned int high;
    unsigned int low;
} Word;

void word2string(Word w, char* s, int min_length) {
    if (!s) return;
    if (min_length < 1) min_length = 1;
    if (min_length > 20) min_length = 20;
    s[0] = (w.area%3) + '0';
    s[1] = 't';
    char tmp[20];
    int i;
    for (i=0;i<10;i++) {
        tmp[19-i] = (w.low % 3) + '0';
        w.low /= 3;
    }
    for (i=0;i<10;i++) {
        tmp[9-i] = (w.high % 3) + '0';
        w.high /= 3;
    }
    i = 0;
    while (tmp[i] == s[0] && i < 20 - min_length) i++;
    int j = 2;
    while (i < 20) {
        s[j] = tmp[i];
        i++;
        j++;
    }
    s[j] = 0;
}

unsigned int crazy_low(unsigned int a, unsigned int d){
    unsigned int crz[] = {1,0,0,1,0,2,2,2,1};
    int position = 0;
    unsigned int output = 0;
    while (position < 10){
        unsigned int i = a%3;
        unsigned int j = d%3;
        unsigned int out = crz[i+3*j];
        unsigned int multiple = 1;
        int k;
        for (k=0;k<position;k++)
            multiple *= 3;
        output += multiple*out;
        a /= 3;
        d /= 3;
        position++;
    }
    return output;
}

Word zero() {
    Word result = {0, 0, 0};
    return result;
}

Word increment(Word d) {
    d.low++;
    if (d.low >= 59049) {
        d.low = 0;
        d.high++;
        if (d.high >= 59049) {
            fprintf(stderr,"error: overflow\n");
            exit(1);
        }
    }
    return d;
}

Word decrement(Word d) {
    if (d.low == 0) {
        d.low = 59048;
        d.high--;
    }else{
        d.low--;
    }
    return d;
}

Word crazy(Word a, Word d){
    Word output;
    unsigned int crz[] = {1,0,0,1,0,2,2,2,1};
    output.area = crz[a.area+3*d.area];
    output.high = crazy_low(a.high, d.high);
    output.low = crazy_low(a.low, d.low);
    return output;
}

Word rotate_r(Word d){
    unsigned int carry_h = d.high%3;
    unsigned int carry_l = d.low%3;
    d.high = 19683 * carry_l + d.high / 3;
    d.low = 19683 * carry_h + d.low / 3;
    return d;
}

// last_initialized: if set, use to fill newly generated memory with preinitial values...
Word* ptr_to(Word** mem[], Word d, unsigned int last_initialized) {
    if ((mem[d.area])[d.high]) {
        return &(((mem[d.area])[d.high])[d.low]);
    }
    (mem[d.area])[d.high] = (Word*)malloc(59049 * sizeof(Word));
    if (!(mem[d.area])[d.high]) {
        fprintf(stderr,"error: out of memory.\n");
        exit(1);
    }
    if (last_initialized) {
        Word repitition[6];
        repitition[(last_initialized-1) % 6] =
                ((mem[0])[(last_initialized-1) / 59049])
                    [(last_initialized-1) % 59049];
        repitition[(last_initialized) % 6] =
                ((mem[0])[last_initialized / 59049])
                    [last_initialized % 59049];
        unsigned int i;
        for (i=0;i<6;i++) {
            repitition[(last_initialized+1+i) % 6] =
                    crazy(repitition[(last_initialized+i) % 6],
                        repitition[(last_initialized-1+i) % 6]);
        }
        unsigned int offset = (59049*d.high) % 6;
        i = 0;
        while (1){
            ((mem[d.area])[d.high])[i] = repitition[(i+offset)%6];
            if (i == 59048) {
                break;
            }
            i++;
        }
    }
    return &(((mem[d.area])[d.high])[d.low]);
}

unsigned int get_instruction(Word** mem[], Word c,
        unsigned int last_initialized,
        int ignore_invalid) {
    Word* instr = ptr_to(mem, c, last_initialized);
    unsigned int instruction = instr->low;
    instruction = (instruction+c.low + 59049 * c.high
            + (c.area==1?52:(c.area==2?10:0)))%94;
    return instruction;
}

int main(int argc, char* argv[]) {
    Word** memory[3];
    int i,j;
    for (i=0; i<3; i++) {
        memory[i] = (Word**)malloc(59049 * sizeof(Word*));
        if (!memory) {
            fprintf(stderr,"not enough memory.\n");
            return 1;
        }
        for (j=0; j<59049; j++) {
            (memory[i])[j] = 0;
        }
    }
    Word a, c, d;
    unsigned int result;
    FILE* file;
    if (argc < 2) {
        // read program code from STDIN
        file = stdin;
    }else{
        file = fopen(argv[1],"rb");
    }
    if (file == NULL) {
        fprintf(stderr, "File not found: %s\n",argv[1]);
        return 1;
    }
    a = zero();
    c = zero();
    d = zero();
    result = 0;
    while (!feof(file)){
        unsigned int instr;
        Word* cell = ptr_to(memory, d, 0);
        (*cell) = zero();
        result = fread(&cell->low,1,1,file);
        if (result > 1)
            return 1;
        if (result == 0 || cell->low == 0x1a || cell->low == 0x04)
            break;
        instr = (cell->low + d.low + 59049*d.high)%94;
        if (cell->low == ' ' || cell->low == '\t' || cell->low == '\r'
                || cell->low == '\n');
        else if (cell->low >= 33 && cell->low < 127 &&
                (instr == 4 || instr == 5 || instr == 23 || instr == 39
                    || instr == 40 || instr == 62 || instr == 68
                    || instr == 81)) {
            d = increment(d);
        }
    }
    if (file != stdin) {
        fclose(file);
    }
    unsigned int last_initialized = 0;
    while (1){
        *ptr_to(memory, d, 0) = crazy(*ptr_to(memory, decrement(d), 0),
                *ptr_to(memory, decrement(decrement(d)), 0));
        last_initialized = d.low + 59049*d.high;
        if (d.low == 59048) {
            break;
        }
        d = increment(d);
    }
    d = zero();

    unsigned int step = 0;
    while (1) {
        unsigned int instruction = get_instruction(memory, c,
                last_initialized, 0);
        step++;
        switch (instruction){
            case 4:
                c = *ptr_to(memory,d,last_initialized);
                break;
            case 5:
                if (!a.area) {
                    printf("%c",(char)(a.low + 59049*a.high));
                }else if (a.area == 2 && a.low == 59047
                        && a.high == 59048) {
                    printf("\n");
                }
                break;
            case 23:
                a = zero();
                a.low = getchar();
                if (a.low == EOF) {
                    a.low = 59048;
                    a.high = 59048;
                    a.area = 2;
                }else if (a.low == '\n'){
                    a.low = 59047;
                    a.high = 59048;
                    a.area = 2;
                }
                break;
            case 39:
                a = (*ptr_to(memory,d,last_initialized)
                        = rotate_r(*ptr_to(memory,d,last_initialized)));
                break;
            case 40:
                d = *ptr_to(memory,d,last_initialized);
                break;
            case 62:
                a = (*ptr_to(memory,d,last_initialized)
                        = crazy(a, *ptr_to(memory,d,last_initialized)));
                break;
            case 81:
                return 0;
            case 68:
            default:
                break;
        }

        Word* mem_c = ptr_to(memory, c, last_initialized);
        mem_c->low = translation[mem_c->low - 33];

        c = increment(c);
        d = increment(d);
    }
    return 0;
}

It's working!

It's working

\$\endgroup\$
  • 1
    \$\begingroup\$ This is just insane. \$\endgroup\$ – MilkyWay90 Aug 31 at 13:05
  • \$\begingroup\$ To passersby, the byte count is 7.784MB, not 7.784GB. I interpreted the comma as as grouping the thousands rather than a decimal point at first. \$\endgroup\$ – Potato44 Aug 31 at 14:32
  • \$\begingroup\$ @Potato44 we use comma as decimal separator in Poland, using dot as such is forbidden. \$\endgroup\$ – Krzysztof Szewczyk Aug 31 at 14:33
5
\$\begingroup\$

Python 3, 84 83 bytes

f=lambda p,i=0:p and chr(126-(i+b"WV@:-zyg"["*jpovi<".find(p[0])])%94)+f(p[1:],i+1)

Try it online!

This is mostly a math problem about simplifying the computation, plus some golfing after getting the math done. The ungolfed version of code is shown below.

Ungolfed, non-recursive version

def convert(prog):
    offsets = dict(zip("*jpovi</", [87, 86, 64, 58, 45, 122, 121, 103]))  # ASCII encoded to "WV@:-zyg"
    output = ""
    for pos, c in enumerate(prog):
        output += chr(126-(offsets[c]+pos)%94)
    return output

Try it online!

\$\endgroup\$
5
\$\begingroup\$

JavaScript (Node.js), 69 bytes

s=>(B=Buffer)(s).map((c,i)=>33+(B(" #{T BAe0d")[c%11]+94-i%94)%94)+''

Try it online!

How?

Conveniently, Malbolge commands can easily be turned into a value in \$[1..9]\$ by just taking their ASCII code modulo \$11\$.

 char. | ASCII code | mod 11
-------+------------+--------
  '*'  |      42    |   9
  'j'  |     106    |   7
  'p'  |     112    |   2
  'o'  |     111    |   1
  'v'  |     118    |   8
  'i'  |     105    |   6
  '<'  |      60    |   5
  '/'  |      47    |   3
\$\endgroup\$
3
\$\begingroup\$

Perl 6, 65 55 53 bytes

*.ords>>.&{(' #{T BAe0d'.ords[$_%11]-$++)%94+33}.chrs

Try it online!

Uses the mod 11 trick from Arnaud's answer

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

05AB1E, 32 31 23 22 bytes

žQ¦•4¡ˆ¶ü]₁η₃•₃вIÇèā-è

-8 bytes creating a port of NickKennedy's Jelly answer, so make sure to upvote him!!
-1 byte thanks to @Grimy.

Outputs as a list of characters.

Try it online or verify all test cases.

Explanation:

   •4¡ˆ¶ü]₁η₃•          # Push compressed integer 82767635194143615015
              ₃в        # Converted to base-95 as list: [1,36,30,85,0,67,66,8,49,7,0]
                IÇ      # Push the input and convert each character to its unicode value
                  è     # Index each into the list we created
                   ā    # Push an integer list in the range [0, length] 
                        # (without popping the list itself)
                    -   # Subtract it from the previous list
žQ                      # Push builtin with all printable ASCII characters,
  ¦                     # and remove the leading space
                     è  # Index the values of the list into the ASCII characters
                        # (after which the result is output implicitly)

See this 05AB1E tip of mine (section How to compress large integers? and How to compress integer lists?) to understand why •4¡ˆ¶ü]₁η₃• is 82767635194143615015 and •4¡ˆ¶ü]₁η₃•₃в is [1,36,30,85,0,67,66,8,49,7,0].

\$\endgroup\$
  • \$\begingroup\$ •1ÃQWý₂Ýδ9•86в -> •4¡ˆ¶ü]₁η₃•₃в \$\endgroup\$ – Grimy Aug 28 at 16:01
  • \$\begingroup\$ @Grimy Thanks :) \$\endgroup\$ – Kevin Cruijssen Aug 28 at 16:15
2
\$\begingroup\$

Perl 5 (-p), 53, 51 bytes

saving 2 bytes, using de{#0ABT instead of '(>DQbcu so that 61 no longer needed

y;*jpovi</;de{#0ABT;;s/./chr 33+(-"@-"+ord$&)%94/ge

TIO

first answer was

y;*jpovi</;'(>DQbcu;;s/./chr 33+(61-"@-"+ord$&)%94/ge

TIO

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

Japt, 24 23 bytes

Port of Nick's Jelly solution

;£EÅgYn" #T BA0 "cXc

Try it

;£EÅgYn"..."cXc     :Implicit input of string
 £                  :Map each character X at 0-based index Y
; E                 :ASCII
   Å                :Slice of the first character (space)
    g               :Get character at index
     Y              :  Increment Y
      n             :  Subtract from
       "..."        :    Literal string (Codepoints: 32,35,29,84,32,66,65,7,48,6,32)
            c       :    Codepoint at index
             Xc     :      Codepoint of X
\$\endgroup\$
1
\$\begingroup\$

Retina 0.8.2, 50 bytes

T`*j\p\ovi</`'(>DQbcu
.
$.`$* $&¶
+T`!p`~_p` .¶
¶

Try it online! Link includes test cases. Explanation:

T`*j\p\ovi</`'(>DQbcu

Make the transliteration as described in the question. p (described below) and o have special meaning to Transliterate so they need to be quoted.

.
$.`$* $&¶

List each character on its own line, preceded by a number of spaces according to its index i.e. what the program counter would be.

+T`!p`~_p` .¶

Repeatedly cyclically decrement the last character on each line, deleting the preceding space each time, until all of the spaces have been deleted. The p stands for printable ASCII i.e. -~, however we want the ! to map to ~ so that is transliterated first, and then the _ causes the space in the match to be deleted, while the remaining characters get transliterated one character code at a time.

Join all of the characters back together.

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

Charcoal, 23 bytes

⭆S§Φγμ⁻℅§ #{T BAe0d ℅ικ

Try it online! Link is to verbose version of code. Port of @Arnauld's JavaScript answer. Explanation:

 S                      Input string
⭆                       Map over characters and join
                     ι  Current character
                    ℅   ASCII code
        §               Cyclically indexed into
          #{T BAe0d     Literal string ` #{T BAe0d `
       ℅                ASCII code
      ⁻                 Subtract
                      κ Current index
  §                     Cyclically indexed into
    γ                   Printable ASCII
   Φ                    Filtered on
     μ                  Inner index (i.e. skip initial space)
                        Implicitly print
\$\endgroup\$
1
\$\begingroup\$

C# (Visual C# Interactive Compiler), 62 bytes

s=>s.Select((c,i)=>(char)(33+(" #{T BAe0d"[c%11]+94-i%94)%94))

Try it online!

Port of @Arnaulds JavaScript answer. One if the rare instances where C# is shorter!

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

Haskell, 135 bytes

t=h"*jpovi</"(fromEnum<$>"'(>DQbcu")
h(x:m)(y:n)i|i==x=y|1<2=h m n i
a n|n<33=a(n+94)|1<2=n
f=(toEnum.a<$>).(zipWith(+)[0,-1..]).(t<$>)

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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