35
\$\begingroup\$

One of easiest code written by a programming language is a program printing sequence of characters (ex. "Hello, world!"). However, some esoteric programming languages like Brainfuck, even this simplest code is quite annoying to write.

Your task is to write a program (don't have to written in brainfuck), which prints a (minimum-length) Brainfuck program printing the given text.

Input

A sequence of characters (between 1 and 255) is given by any format (variable, argument, stdin, file, ...).

Output

Output is a valid (no non-matching [ and ]) brainfuck code (assume unsigned 8-bit wrapping cell and unlimited number of cells to left and right) printing exact string that was given as input.

For example, one possible output for input A is ++++++++[<++++++++>-]<+..

Your program shouldn't take long time (>2m) to run.

The BF program shouldn't take long time (>10s) to run.

Scoring

(Notice : current scoring method may change, since it is not easy to calculate...)

The length of program (generating BF code) itself doesn't matter. However, hard-coding BF codes in the program code is not OK. Only acceptable range (ex. a BF code printing a single character 0x01 : +.) of BF codes might be hard-coded.

The score is sum of length of BF codes printing these strings.

  • A string Hello, world! appended with a single 0x0A (\n) (i.e. the "Hello, world!" program)
  • Single character from 0x01 ~ 0xFF
    • Sum of length of these 255 BF codes are multiplied by 1/16, rounded, and added to the score.
  • List of first 16 strings, generated by splitting a random sequence of bytes generated on 11-11-11 by 0x00, removing all zero-length strings.
  • Lenna.png, removing all 0x00s.
  • Lyrics of the song 99 bottles of beer, starting with 99 bottles~, newlines are 0x0A, paragraphs are separated by two 0x0As, and no newline character at the end.
  • Other strings you may provide.

Your program may include calculating the score of itself.

Of-course, lowest-score code will be the winner.

\$\endgroup\$
6
  • \$\begingroup\$ Duplicate (although better phrased) of codegolf.stackexchange.com/questions/3450/… \$\endgroup\$
    – copy
    Apr 8, 2012 at 17:01
  • 4
    \$\begingroup\$ This seems rather hard to calculate the scores. It is a lot of different files we need to track down and run through. What is the point of 'Other strings you may provide'. Why would I add more if it will add to my score? \$\endgroup\$
    – captncraig
    Apr 9, 2012 at 3:28
  • 1
    \$\begingroup\$ Lenna.png is going to dominate the score, as it is by far the largest input. Maybe normalize a bit by size? \$\endgroup\$ Apr 9, 2012 at 4:35
  • 1
    \$\begingroup\$ Minimum-length code for 'A' is ----[---->+<]>++. \$\endgroup\$
    – Scony
    Feb 25, 2014 at 12:37
  • 2
    \$\begingroup\$ OP obviously doesn't care about this challenge. Let's edit the scoring rules to something sensible? Currently, only one answer (unsuccessfully) tried to use these rules, so changing rules will not invalidate answers. \$\endgroup\$
    – anatolyg
    Jun 29, 2016 at 12:31

11 Answers 11

18
\$\begingroup\$

In Java, computes a short BF snippet which can convert any number to any other number. Each output byte is generated by either transforming the last output byte or a fresh 0 on the tape.

The snippets are generated in three ways. First by simple repetitions of + and - (e.g. ++++ converts 7 to 11), by combining known snippets (e.g. if A converts 5 to 50 and B converts 50 to 37, then AB converts 5 to 37) and simple multiplications (e.g. [--->+++++<] multiplies the current number by 5/3). The simple multiplications take advantage of wraparound to generate unusual results (e.g. --[------->++<]> generates 36 from 0, where the loop executes 146 times, with a total of 4 descending and 1 ascending wraparounds).

I'm too lazy to compute my score, but it uses about 12.3 BF operations per byte on Lenna.png.

import java.io.*;

class shortbf {
    static String repeat(String s, int n) {
        StringBuilder b = new StringBuilder();
        for (int i = 0; i < n; i++) b.append(s);
        return b.toString();
    }

    // G[x][y]: BF code that transforms x to y.                                                     
    static String[][] G = new String[256][256];
    static {
        // initial state for G[x][y]: go from x to y using +s or -s.                                
        for (int x = 0; x < 256; x++) {
            for (int y = 0; y < 256; y++) {
                int delta = y - x;
                if (delta > 128) delta -= 256;
                if (delta < -128) delta += 256;

                if (delta >= 0) {
                    G[x][y] = repeat("+", delta);
                } else {
                    G[x][y] = repeat("-", -delta);
                }
            }
        }

        // keep applying rules until we can't find any more shortenings                             
        boolean iter = true;
        while (iter) {
            iter = false;

            // multiplication by n/d                                                                
            for (int x = 0; x < 256; x++) {
                for (int n = 1; n < 40; n++) {
                    for (int d = 1; d < 40; d++) {
                        int j = x;
                        int y = 0;
                        for (int i = 0; i < 256; i++) {
                            if (j == 0) break;
                            j = (j - d + 256) & 255;
                            y = (y + n) & 255;
                        }
                        if (j == 0) {
                            String s = "[" + repeat("-", d) + ">" + repeat("+", n) + "<]>";
                            if (s.length() < G[x][y].length()) {
                                G[x][y] = s;
                                iter = true;
                            }
                        }

                        j = x;
                        y = 0;
                        for (int i = 0; i < 256; i++) {
                            if (j == 0) break;
                            j = (j + d) & 255;
                            y = (y - n + 256) & 255;
                        }
                        if (j == 0) {
                            String s = "[" + repeat("+", d) + ">" + repeat("-", n) + "<]>";
                            if (s.length() < G[x][y].length()) {
                                G[x][y] = s;
                                iter = true;
                            }
                        }
                    }
                }
            }

            // combine number schemes                                                               
            for (int x = 0; x < 256; x++) {
                for (int y = 0; y < 256; y++) {
                    for (int z = 0; z < 256; z++) {
                        if (G[x][z].length() + G[z][y].length() < G[x][y].length()) {
                            G[x][y] = G[x][z] + G[z][y];
                            iter = true;
                        }
                    }
                }
            }
        }
    }
    static void generate(String s) {
        char lastc = 0;
        for (char c : s.toCharArray()) {
            String a = G[lastc][c];
            String b = G[0][c];
            if (a.length() <= b.length()) {
                System.out.print(a);
            } else {
                System.out.print(">" + b);
            }
            System.out.print(".");
            lastc = c;
        }
        System.out.println();
    }

    static void genFile(String file) throws IOException {
        File f = new File(file);
        int len = (int)f.length();
        byte[] b = new byte[len];
        InputStream i = new FileInputStream(f);
        StringBuilder s = new StringBuilder();
        while (true) {
            int v = i.read();
            if (v < 0) break;
            if (v == 0) continue; // no zeros                                                       
            s.append((char)v);
        }
        generate(s.toString());
    }
    public static void main(String[] args) throws IOException {
        genFile(args[0]);
    }
}
\$\endgroup\$
1
  • 2
    \$\begingroup\$ I know that I'm about two and a half years late and this isn't golf, but the repeat method could just be return new String(new char[length]).replaceAll("\0", str); \$\endgroup\$
    – xenia
    Oct 19, 2015 at 5:03
14
\$\begingroup\$

Well here's about the worst possible solution, although a rather nice looking one in Brainfuck itself:

++++++[>+++++++>+++++++++++++++>+++++++<<<-]>++++>+>+>,[[<.>-]<<<.>.>++.--<++.-->>,]

Score is probably the worst we are likely to see without intentionally making it bad.

Working on calculating actual score.

\$\endgroup\$
3
  • \$\begingroup\$ Could you explain what this does? Reading BF code is rather difficult. \$\endgroup\$
    – BMac
    Mar 4, 2015 at 21:55
  • 6
    \$\begingroup\$ For each byte of input it simply prints N +'s and a . \$\endgroup\$
    – captncraig
    Mar 4, 2015 at 21:56
  • 1
    \$\begingroup\$ With I believe a [-] to clear the cell between each character. \$\endgroup\$
    – captncraig
    Mar 4, 2015 at 22:01
8
\$\begingroup\$

Python 3.x

Well, I'm not going to win any prizes for the shortest output code but maybe for the program to generate the code...

x=input();o=''
for i in x:
 a=ord(i)
 o+="+"*a+".[-]"
print(o)

'Hello, World!\n':

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+.[-]+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++++++
++++.[-]++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++++++++++++++++++++++++++++++++.[-]+++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++.[-]++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++.[-]++++++++++++++++++++++++++++++++
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.[-]
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
++++++++++++++++++++.[-]+++++++++++++++++++++++++++++++++.[-]++++++++++.[-]
\$\endgroup\$
3
  • 1
    \$\begingroup\$ Accidentally answered to Madisons answer. Shorter generator: print("".join(["+"*ord(i)+".[-]"for i in input()])) \$\endgroup\$
    – FatalError
    Dec 8, 2017 at 3:25
  • 1
    \$\begingroup\$ You can golf your program by 2 chars by replacing .[-] with .> \$\endgroup\$
    – MilkyWay90
    May 5, 2019 at 16:44
  • \$\begingroup\$ 47 bytes \$\endgroup\$
    – MilkyWay90
    May 5, 2019 at 16:55
8
\$\begingroup\$

I'm not sure how good it is, but I had fun writing this. (In Clojure...)

(ns bf.core)
(def input "Hello world!")
(defn abs [x] (if (and (< x 0) (= (type x) (type 1))) (* x -1) x)) 
(defn sqrt? [x y] (if (= (* y y) x) true false) )
(defn sqrt [x] (for [y (range x) :when (sqrt? x y)] y) )
(defn broot [x]
;  Run this using trampoline, e.g., (trampoline broot 143)
  (if (< x 2) nil)
  (let [y (sqrt x)]
    (if (not= (seq y) nil)
      (first y)
      (broot (dec x) )
      )
    )
  )
(defn factor [x]
  (if (<= x 2) x)
  (for [y (range (- x 1) 1 -1)
        :when (= ( type (/ x y) ) (type 1) )
        ]
    y)
  )

(defn smallest_factors [x]
  (let [ facts (factor x) ]
  (if (= (count facts) 2) facts)
  (if (< (count facts) 2) (flatten [facts facts])
    (let [ y (/ (dec (count facts) ) 2)
          yy (nth facts y)
          z (inc y)
          zz (nth facts z)
          ]
      (if (= (* yy zz) x ) [yy zz] [yy yy])
      )
    )
    )
  )

(defn smallestloop [x]
  (let [ facts (smallest_factors x)
        fact (apply + facts)
        sq (trampoline broot x)
        C (- x (* sq sq) )]
    (if (and (< fact (+ (* 2 sq) C) ) (not= fact 0))
      facts
      [sq sq C])
    )
  )
(def lastx nil)
;Finally!
(defn buildloop [x]
  (if (nil? lastx)
     (let [pluses (smallestloop x)]
       (apply str
              (apply str (repeat (first pluses) \+))
              "[>"
              (apply str (repeat (second pluses) \+))
              "<-]>"
              (if (and (= (count pluses) 3) (> (last pluses) 0))
                (apply str(repeat (last pluses) \+))
              )
              "."
              )
    )
    (let [diff (abs (- x lastx) )]
      (if (< diff 10)
        (if (> x lastx)
          (apply str
               (apply str (repeat diff \+))
               "."
                 )
          (apply str
                 (apply str (repeat diff \-))
                 "."
                 )
          )
        (let [signs (smallestloop diff)]
          (apply str
             "<"
             (apply str (repeat (first signs) \+))
             "[>"
             (if (> x lastx)
               (apply str (repeat (second signs) \+))
               (apply str (repeat (second signs) \-))
             )
             "<-]>"
             (if (and (= (count signs) 3) (> (last signs) 0))
               (if (> x lastx)
                 (apply str(repeat (last signs) \+))
                 (apply str(repeat (last signs) \-))
               )
             )
             "."
           )
         )
        )
      )
    )
  )
(for [x (seq input)
  :let [y (buildloop (int x))]
      ]
  (do 
   (def lastx (int x))
   y
   )
  )

There are probably more efficient solutions, and more elegant ones, but this follows my thought pattern somewhat linearly, so it was easiest.

\$\endgroup\$
8
\$\begingroup\$

Score: 4787486 4143940 4086426 (without randomly generated data)

(4085639 of that are from Lenna.png. That's 99.98%)

I don't get the part with the random data. Don't I need an account which I have to pay for to get the data?

Pretty naive. Here's the generated code for "1Aa" (49, 65, 97) with a little documentation:

                   // field 0 and 1 are loop counters.
                   // The fields 2, 3 and 4 are for "1", "A" and "a"
++++++++++[        // do 10 times
    >
    ++++++++++[    // do 10 times
        >          // "1" (49) is below 50 so we don't need to do anything here
        >+         // When the loops are done, this part will have increased the value of field 3 by 100 (10 * 10 * 1)
        >+         // same as above
        <<<-       // decrease inner loop counter
    ]
    >+++++         // this will add 50 (10 * 5) to field 2, for a total of 50
    >----          // subtract 40 from field 3, total of 60
    >              // Nothing done, value stays at 100
    <<<<-          // decrease outer loop counter
]
>>-.               // subtract 1 from field 2, total now: 49, the value for "1"
>+++++.            // add 5 to field 3, makes a total of 65, the value for "A"
>---.              // subtract 3 from field 4, total of 97, the value for "a"

The Java code is a bit ugly but it works. The generated instruction per input byte ratio is probably better the higher the average byte value is.

If you want to run it, you have to put Lenna.png in the same directory as the .class file. It prints the score to console and writes the generated BF code into a file called "output.txt".

import java.io.File;
import java.io.FileInputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Arrays;


public class BrainFuckGenerator {
    public static CharSequence generate(byte[] bytes) {
        final StringBuilder brainfuckBuilder = new StringBuilder();
        for(int j = 0; j<10; j++)
                brainfuckBuilder.append("+");

        brainfuckBuilder.append("[>");

        for(int j = 0; j<10; j++)
            brainfuckBuilder.append("+");

        brainfuckBuilder.append("[");

        final StringBuilder singles = new StringBuilder();
        final StringBuilder tens = new StringBuilder();
        final StringBuilder goBack = new StringBuilder();

        for(byte b: bytes) {
            brainfuckBuilder.append(">");
            for(int j=0; j<(b/100); j++) {
                brainfuckBuilder.append("+");
            }

            tens.append(">");
            if((b - (b/100)*100)/10 <= 5) {
                for(int j=0; j<(b - (b/100)*100)/10; j++) {
                    tens.append("+");
                }
            } else {
                brainfuckBuilder.append("+");
                for(int j=0; j<10 - (b - (b/100)*100)/10; j++) {
                    tens.append("-");
                }
            }

            singles.append(">");
            if(b%10 <= 5) {
                for(int j=0; j<b%10; j++) {
                    singles.append("+");
                }
            } else {
                tens.append("+");
                for(int j=0; j<10 - (b%10); j++) {
                    singles.append("-");
                }
            }
            singles.append(".");

            goBack.append("<");
        }
        goBack.append("-");

        brainfuckBuilder
            .append(goBack)
            .append("]")
            .append(tens)
            .append("<")
            .append(goBack)
            .append("]>")
            .append(singles);

        return brainfuckBuilder;
    }

    public static void main(String[] args) {
        /* Hello, World! */
        int score = score("Hello, world!"+((char)0xA));

        /* 255 single chars */
        int charscore = 0;
        for(char c=1; c<=0xff; c++)
            charscore += score(String.valueOf(c));

        score += Math.round(((double)charscore)/16);

        /* Lenna */
        final File lenna = new File("Res/Lenna.png");
        final byte[] data = new byte[(int)lenna.length()];
        int size = 0;
        try(FileInputStream input = new FileInputStream(lenna)) {
            int i, skipped=0;
            while((i = input.read()) != -1)
                if(i == 0)
                    skipped++;
                else
                    data[size++ - skipped] = (byte)(i&0xff);
        } catch (IOException e) {
            e.printStackTrace();
        }

        score += score(Arrays.copyOf(data, size), "Lenna");

        /* 99 Bottles */
        final StringBuilder bottleBuilder = new StringBuilder();
        for(int i=99; i>2; i--) {
            bottleBuilder
                .append(i)
                .append(" bottles of beer on the wall, ")
                .append(i)
                .append(" bottles of beer.")
                .append((char) 0xa)
                .append("Take one down and pass it around, ")
                .append(i-1)
                .append(" bottles of beer on the wall.")
                .append((char) 0xa)
                .append((char) 0xa);

        }

        bottleBuilder
            .append("2 bottles of beer on the wall, 2 bottles of beer.")
            .append((char) 0xa)
            .append("Take one down and pass it around, 1 bottle of beer on the wall.")
            .append((char) 0xa)
            .append((char) 0xa)
            .append("No more bottles of beer on the wall, no more bottles of beer. ")
            .append((char) 0xa)
            .append("Go to the store and buy some more, 99 bottles of beer on the wall.");

        score(bottleBuilder.toString(), "99 Bottles");
        System.out.println("Total score: "+score);
    }

    private static int score(String s) {
        return score(s, null);
    }

    private static int score(String s, String description) {
        final CharSequence bf = generate(s.getBytes());
        try(FileWriter writer = new FileWriter("Res/output.txt", true)) {
            writer.write((description == null ? s : description));
            writer.write(NL);
            writer.write("Code:");
            writer.write(NL);
            writer.write(bf.toString());
            writer.write(NL+NL);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bf.length();
    }

    private static int score(byte[] bytes, String description) {
        final CharSequence bf = generate(bytes);
        try(FileWriter writer = new FileWriter("Res/output.txt", true)) {
            if(description != null) {
                writer.write(description);
                writer.write(NL);
            }
            writer.write("Code:");
            writer.write(NL);
            writer.write(bf.toString());
            writer.write(NL+NL);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return bf.length();
    }

    private static final String NL = System.getProperty("line.separator");
}

I'm going to make some small improvements but probably not much. Done.

\$\endgroup\$
1
  • \$\begingroup\$ This is broken, the generated BF outputs NUL bytes or '?' characters depending on the locale for any character not in 1..127. Plain ASCII (1-127) seems to be fine though. Noticing that (byte)s are signed and carefully choosing a locale gets a lot of them (taking the score over 5800000) but there are still several thousand NULs from the bf conversion of Lenna.png. So there's something else too. \$\endgroup\$ Sep 5, 2015 at 22:52
6
\$\begingroup\$

BrainF**k

I'm a pretty bad BF programmer, so this answer is probably pretty inefficient. I'm unsure of the score, but it should perform slightly better than the existing answer on your average text. Rather than zeroing the cell out after every character, this one will "adjust" to a new character with subtraction if the previous character given is larger.

>>++++++[-<+++++++>]<+>>+++++[-<+++++++++>]>+++++[-<+++++++++>]<+>>,[-<+>>>>>>+<<<<<]<[-<<<.>>>]<.>>+>,[[-<<+>>>+>>+<<<]>>>>[-<<+>>]<[->+<]<<[->-[>]<<]<[->->[-<<<<<<.>>>>>>]<<]>+[-<<->>>[-<<<<<<.>>>>>>]<]<<<[>]<<.>[-]>+>,]

(Note, this is code I wrote a long time ago, and have repurposed for this competition. I sincerely hope I've done the conversion correctly, but if it fails for any input let me know.)

A version showing the state of the tape throughout the code:

>>++++++[-<+++++++>]<+>             [0 '+' 0]
                                           ^
>+++++[-<+++++++++>]                [0 '+' '-' 0]
                                               ^
>+++++[-<+++++++++>]<+>             [0 '+' '-' '.' 0]
                                                   ^
>,[-<+>>>>>>+<<<<<]                 [0 '+' '-' '.' a 0 0 0 0 0 a]
                                                     ^
<[-<<<.>>>]<.>>+>                   [0 '+' '-' '.' 0 1 0 0 0 0 a]
                                                       ^
,[[-<<+>>>+>>+<<<]                  [0 '+' '-' '.' b 1 0 b 0 b a]
                                    [b is not 0]       ^
>>>>[-<<+>>]<[->+<]                 [0 '+' '-' '.' b 1 0 b a 0 b]
                                                             ^    
<<[->-[>]<<]

<[->->[-<<<<<<.>>>>>>]<<]           

>+[-<<->>>[-<<<<<<.>>>>>>]<]        [0 '+' '-' '.' b 0 0 0 0 0 b]
                                                       ^|^
                                    [OUTPUT ONE CHARACTER BY THIS POINT]
<<<[>]<<.>[-]>                      [0 '+' '-' '.' 0 0 0 0 0 0 b]
                                                     ^
+>,]                                [End when b == 0]
                                    [GOAL: '+' '-' '.' b 1 0 b a 0 a]

Generated code for Hello, World!:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.+++++++++++++++++++++++++++++.+++++++..+++.-------------------------------------------------------------------.------------.+++++++++++++++++++++++++++++++++++++++++++++++++++++++.++++++++++++++++++++++++.+++.------.--------.-------------------------------------------------------------------.

This is my first answer on CG.SE! If I've screwed something up, let me know!

\$\endgroup\$
4
\$\begingroup\$

><>

i:&v
.21<>i:0(?;:&:@(4g62p$:&-01-$:@0(?*l2=?~02.
>:?!v"+"o1-
"."~<.15o
+-

I wrote this in response to a question marked for a duplicate, and even though this isn't the greatest golf (for this specific question, at least) I figured it'd be kind of a waste if I didn't share it in all of its disgustingly gibberish-y glory. Really, I'm half-surprised it even works. I'll take any suggestions to golf it down since that was my main goal in its creation.

As a side note, in the second line the beginning three characters .21 could be replaced with v followed by two spaces if that makes it any easier to read. I don't like to see spaces in my ><> programs because that means there's wasted space (literally). It also is a remnant from one of many prototypes.

The way it functions is really simple, and quite frankly I'd have a difficult time thinking of a way to implement another algorithm. It prints however many "+"s need to be printed for the first character, and then prints more "+"s or "-"s as needed for each additional character, separating each section with periods. What I find cool about the program is that it modifies its own source code so that it prints "+" or "-" (it replaces the "+" on line 3 with the appropriate character after determining whether the current character is greater than or less than the previous one).

Output for Hello, World!:

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++.+++++++++++++++++++++++++++++.+++++++..+++.-------------------------------------------------------------------.------------.+++++++++++++++++++++++++++++++++++++++++++++++++++++++.++++++++++++++++++++++++.+++.------.--------.-------------------------------------------------------------------.

I might score this the way it was intended to be scored, but I'm almost positive I'd lose and I don't entirely know how to read something like lenna.png in ><>.

If this answer interests you and you'd like an explanation, by all means ask, but for now I'll leave it without one just because of how twisty and turny it is.

EDIT 1: It's been a while but I was able to golf off 2 bytes with an almost complete overhaul of the way the program decides whether to print a plus or a minus. It's a somewhat disappointing return for a large overhaul but at least it works.

\$\endgroup\$
1
  • \$\begingroup\$ This gibberish program makes another gibberish program! This is the greatest program i've ever seen! \$\endgroup\$
    – Aequitas
    Oct 19, 2015 at 2:46
2
\$\begingroup\$

Kotlin

First I must admit that about half of this is shamelessly stolen from Keith Randel's answer. I literally copied and pasted his code for finding multiplications into mine. It is very well done. I have made 2 modifications to his code.

  1. It is very easy to reverse the direction of any of the transitions Keith's code produces. All you have to do is replace < with > and > with <. Ex. +[->++<]> becomes +[-<++>]<. This means that equally, good code* that only uses 2 cells can be produced.
  2. The second thing I did was to add a hash. To add a hash the code simply includes a > after the .. Then for all subsequent characters, the program calculates if the code to move to the hash (such as <<<.>>>) is shorter than the regular code to produce this output. To find the locations for the hash the program loops through each character of the input and checks if hashing that character decreases the length of the program. It only hashes each type of character once which is slightly inefficient but is necessary to make the program not run like a snail.

Speaking of time complexity my program is quadratic time as opposed to Keith's which is linear. This is because the full code for any hash configuration has to be generated for each character. It is still possible to stop the algorithm early and get a better result than you started with as each cache only improves the score.

So how good does it do. Theoretically, it always does better than Keith's however, this isn't quite true. Keith's code can get to zero from any number simply by inserting a > but mine cannot because it would move too far from the hash. As a result, some of my transitions are slightly longer than Keith's meaning I actually achieve a lower score on Lenna.png. However since most of the transitions where starting with zero instead are very small or very large if you only include letters in the printable ASCII range 32 to 126 my program does beat Keith's. So although it can be worse my algorithm is better in practice. Lets look at some numbers.

Numbers

Lenna.png (no 0s), 471470 characters

Keith:  5832730 bf chars,  12.37 bf chars / input
Me:    <5894582 bf chars, <12.5  bf chars / input

Keith + 0.13
Lenna.png (32 to 126), 174177 characters

Keith:  2022451 bf chars,  11.61 bf chars / input 
Me:    <1977716 bf chars,  11.25 bf chars / input

Me + 0.36
Hello, World!\n, 14 characters

Keith:      139 bf chars, 9.93 bf chars / input
Me:         139 bf chars, 9.93 bf chars / input

Even
I went to the store today, 25 characters

Keith:      265 bf chars, 10.6 bf chars / input
Me:         190 bf chars,  7.6 bf chars / input

Me + 3
447 characters of Lorem Ipsum

Keith:     4427 bf chars,  9.9 bf chars / input
Me:        3017 bf chars, 6.75 bf chars / input

Me + 3.15 
1001 random lowercase letters

Keith:     8035 bf chars, 8.03 bf chars / input
Me:        7028 bf chars, 7.02 bf chars / input

Me + 1.01

Code

const val CELL_SIZE = 256
const val WRAPPING = true

enum class Direction {
    LEFT, RIGHT;

    fun opposite() = when (this) {
        LEFT -> RIGHT
        RIGHT -> LEFT
    }
}

data class Transition(
    val code: String,
    val startD: Direction,
    val endD: Direction,
) {
    fun plus(t: Transition) = if (endD == t.startD) {
        Transition(code + t.code, t.startD, t.endD)
    } else {
        val r = t.reverse()
        Transition(code + r.code, t.startD, r.endD)
    }

    fun reverse(): Transition {
        val newCode = code.replace(">", "|").replace("<", ">").replace("|", "<")
        return Transition(newCode, startD.opposite(), endD.opposite())
    }
}

var simpleTransitions = {
    val list = Array(CELL_SIZE) { start ->
        Array(CELL_SIZE) { end ->
            var delta = end - start
            if (WRAPPING) when {
                delta > CELL_SIZE / 2 -> delta -= CELL_SIZE
                delta < -CELL_SIZE / 2 -> delta += CELL_SIZE
            }
            val char = if (delta > 0) "+" else "-"
            Transition(char.repeat(abs(delta)), Direction.LEFT, Direction.LEFT)
        }
    }

    //check for multiplication
    for (x in 0..255) {
        for (n in 1..39) {
            for (d in 1..39) {
                var j = x
                var y = 0
                for (i in 0..255) {
                    if (j == 0) break
                    j = j - d + 256 and 255
                    y = y + n and 255
                }
                if (j == 0) {
                    val s = "[" + "-".repeat(d) + ">" + "+".repeat(n) + "<]>"
                    if (s.length < list[x][y].code.length) {
                        list[x][y] = Transition(s, Direction.LEFT, Direction.RIGHT)
                    }
                }
                j = x
                y = 0
                for (i in 0..255) {
                    if (j == 0) break
                    j = j + d and 255
                    y = y - n + 256 and 255
                }
                if (j == 0) {
                    val s = "[" + "+".repeat(d) + ">" + "-".repeat(n) + "<]>"
                    if (s.length < list[x][y].code.length) {
                        list[x][y] = Transition(s, Direction.LEFT, Direction.RIGHT)
                    }
                }
            }
        }
    }

    for (i in 0 until CELL_SIZE) {
        if (list[i][0].code.length > 3) list[i][0] = Transition("[-]", Direction.LEFT, Direction.LEFT)
    }

    var change = true
    while (change) {
        change = false
        for (i in 0 until CELL_SIZE) {
            for (j in 0 until CELL_SIZE) {
                for (k in 0 until CELL_SIZE) {
                    if (list[i][j].code.length + list[j][k].code.length < list[i][k].code.length) {
                        list[i][k] = list[i][j].plus(list[j][k])
                        change = true
                    }
                }
            }
        }
    }

    println("done")
    list
}()

fun generate(s: String, cells: Int): String {
    var caches: MutableList<Boolean> = MutableList(s.length) { false }
    var currentCost = generateFromCache(s, caches)
    val changes: MutableMap<Char, Int> = mutableMapOf()

    if (cells > 2) {
        for ((i, c) in s.withIndex()) {
            val newCaches = caches.mapIndexed { index, b -> if (s[index] == c) false else b }.toMutableList()
            var lowest: Char? = null
            if (newCaches.count { it } >= cells - 2) {
                lowest = changes.minByOrNull { it.value }!!.key
                newCaches[newCaches.withIndex().indexOfFirst { it.value && s[it.index] == lowest }] = false
            }
            newCaches[i] = true
            val newCost = generateFromCache(s, newCaches)
            if (newCost.length < currentCost.length) {
                println(newCost.length)
                changes[c] = currentCost.length - newCost.length
                currentCost = newCost
                caches = newCaches
                if (lowest != null) changes.remove(lowest)
            }
        }
    }

    return currentCost
}
\$\endgroup\$
1
\$\begingroup\$

my JavaScript solution it`s quick and dirty :)

output for Hello World\n

++++++++++[>+++++++>++++++++++>+++++++++++>+++>+++++++++>+<<<<<<-]>++.>+.>--..+++.>++.>---.<<.+++.------.<-.>>+.>>.

Source:

BfGen("Hello World!\n");
// ++++++++++[>+++++++>++++++++++>+++++++++++>+++>+++++++++>+<<<<<<-]>++.>+.>--..+++.>++.>---.<<.+++.------.<-.>>+.>>.
// Length of BF code: 115
// Score: 8.846153846153847


function BfGen(input) {
    function StringBuilder() {
        var sb = {};

        sb.value = '';
        sb.append = (txt) => sb.value += txt;

        return sb;
    }

    function closest (num, arr) {
        var arr2 = arr.map((n) => Math.abs(num-n))
        var min = Math.min.apply(null, arr2);
        return arr[arr2.indexOf(min)];
    }

    function buildBaseTable(arr) {
        var out = StringBuilder();
        out.append('+'.repeat(10));
        out.append('[')
        arr.forEach(function(cc) {
            out.append('>');
            out.append('+'.repeat(cc/10));    
        });
        out.append('<'.repeat(arr.length));
        out.append('-');

        out.append(']');
        return out.value;
    }

    var output = StringBuilder();

    var charArray = input.split('').map((c) =>c.charCodeAt(0));
    var baseTable = charArray.map((c) => Math.round(c/10) * 10).filter((i, p, s) => s.indexOf(i) === p);

    output.append(buildBaseTable(baseTable));

    var pos = -1;
    charArray.forEach(function (charCode) {
        var bestNum = closest(charCode, baseTable);
        var bestPos = baseTable.indexOf(bestNum);

        var moveChar = pos < bestPos? '>' : '<';
        output.append(moveChar.repeat(Math.abs(pos - bestPos)))
        pos = bestPos;

        var opChar = baseTable[pos] < charCode? '+': '-';
        output.append(opChar.repeat(Math.abs(baseTable[pos] - charCode)));
        output.append('.');
        baseTable[pos] = charCode;
    });

    console.log(output.value)
    console.log('Length of BF code: ' + output.value.length);
    console.log('Score: ' + output.value.length / input.length);
}
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Welcome to the site! You should include the score in the title of your answer. \$\endgroup\$
    – Wheat Wizard
    Jan 18, 2017 at 0:07
  • \$\begingroup\$ i just made the bf generator, original score system has image processing what is not relevant :( Hello world ratio is less then 9 (bf length / original text length) \$\endgroup\$
    – Peter
    Jan 18, 2017 at 2:00
1
\$\begingroup\$

I built something in Java. Didn't calculate the score. Texts with 3 or less characters are encoded with a multiplication per letter e.g. "A" = ++++++++[>++++++++<-]>+.. Texts with more than 3 characters are encoded with a calculated list which is split into 3 areas. The first area is x times 49, then plus x times 7 and finally plus x. For example "A" is 1*49 + 2*7 + 2

public class BFbuilder {
    public static void main(String[] args) {
        String text = "### INSERT TEXT HERE ###";

        if (text.length()<=3){
            String result = "";
            for (char c:text.toCharArray()) {
                result += ">";
                if (c<12) {
                    for (int i=0;i<c;i++) {
                        result += "+";
                    }
                    result += ".>";
                } else {
                    int root = (int) Math.sqrt(c);
                    for (int i = 0; i<root;i++) {
                        result += "+";
                    }
                    result += "[>";
                    int quotient = c/root;
                    for (int i = 0; i<quotient;i++) {
                        result += "+";
                    }
                    result += "<-]>";
                    int remainder = c - (root*quotient);
                    for (int i = 0; i<remainder;i++) {
                        result += "+";
                    }
                    result += ".";
                }
            }
            System.out.println(result.substring(1));
        } else {
            int[][] offsets = new int[3][text.length()];
            int counter = 0;
            String result = "---";

            for(char c:text.toCharArray()) {
                offsets[0][counter] = c/49;
                int temp = c%49;
                offsets[1][counter] = temp/7;
                offsets[2][counter] = temp%7;
                counter++;
            }

            for (int o:offsets[0]) {
                switch (o) {
                case 0: result+=">--";
                break;
                case 1: result+=">-";
                break;
                case 2: result+=">";
                break;
                case 3: result+=">+";
                break;
                case 4: result+=">++";
                break;
                case 5: result+=">+++";
                break;
                }
            }
            result += ">+[-[>+++++++<-]<+++]>----";
            for (int o:offsets[1]) {
                switch (o) {
                case 0: result+=">---";
                break;
                case 1: result+=">--";
                break;
                case 2: result+=">-";
                break;
                case 3: result+=">";
                break;
                case 4: result+=">+";
                break;
                case 5: result+=">++";
                break;
                case 6: result+=">+++";
                break;
                }
            }
            result += ">+[-[>+++++++<-]<++++]>----";
            for (int o:offsets[2]) {
                switch (o) {
                case 0: result+=">---";
                break;
                case 1: result+=">--";
                break;
                case 2: result+=">-";
                break;
                case 3: result+=">";
                break;
                case 4: result+=">+";
                break;
                case 5: result+=">++";
                break;
                case 6: result+=">+++";
                break;
                }
            }
            result += ">+[-<++++]>[.>]";
            System.out.println(result);
        }
    }
}

The provided String "### INSERT TEXT HERE ###" becomes --->-->-->-->-->->->->->->->-->->->->->-->->->->->-->-->-->-->+[-[>+++++++<-]<+++]>---->++>++>++>+>>+>+>->+>++>+>++>->++>++>+>>->+>->+>++>++>++>+[-[>+++++++<-]<++++]>---->--->--->--->+>>-->+++>+++>++>--->+>--->+++>+>--->+>->+++>++>+++>+>--->--->--->+[-<++++]>[.>]

"Hello, World!" becomes --->->>>>>-->-->->>>>>-->+[-[>+++++++<-]<+++]>---->>--->-->-->-->+++>+>++>-->->-->--->+>+[-[>+++++++<-]<++++]>---->->>>>+++>->+>>+++>->>->++>+[-<++++]>[.>]

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

Python 3

print("".join("+"*ord(i)+".[-]"for i in input()))

This is essentially just an slightly improved version of icedvariables' answer. (-1 Byte from Wheat Wizard, -5 from FatalError, -2 from jez)

\$\endgroup\$
4
  • \$\begingroup\$ I believe this is python 3. If so You should include so in your header. If so you can also remove the line break after your :. This could also probably be done as a list comprehension to save bytes. \$\endgroup\$
    – Wheat Wizard
    Jun 15, 2017 at 14:41
  • \$\begingroup\$ -5 bytes with print("".join(["+"*ord(i)+".[-]"for i in input()])) \$\endgroup\$
    – FatalError
    Dec 8, 2017 at 3:12
  • \$\begingroup\$ -2 bytes: lose the square brackets so you're calling join() on a generator expression rather than a list comprehension: print("".join("+"*ord(i)+".[-]"for i in input())) \$\endgroup\$
    – jez
    Jan 28, 2019 at 17:44
  • \$\begingroup\$ -2 bytes: you can simply move to the next cell (the question states you should assume an infinite band in both directions print("".join("+"*ord(i)+".>"for i in input())) (this also reduces the score since you lose 2 bytes in the output) \$\endgroup\$
    – MegaIng
    Feb 8, 2019 at 10:21

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.