Pick a programming language.

In that language, write a function or program that takes as input a string of working code (in the same language).

It should return/output a minified version of the input code. The output should be functionally equivalent to the input.

Answers should contain both

  1. The code that does the minifying (should be readable/non-golfed)
  2. The resulting output after passing your code (that is, the code that does the minifying) in to itself

The shortest output wins.

Edit: notes explaining the minification methods you used are appreciated.

Edit: obviously, minification methods could vary from language to language, but a good-faith attempt at writing a minifier for this challenge would most likely perform actions such as

  • Removing unnecessary whitespace
  • Removing unnecessary newlines
  • Removing comments
  • Shortening variable names
  • There is a definite tension between wanting the code to be "readable/non-golfed" and wanting it to minify to as short as possible. There's more to golfing than using one-character names and removing whitespace. – Peter Taylor Feb 1 '12 at 20:47
  • @PeterTaylor I agree. Do you have any suggestions to help resolve that tension and encourage people to submit interesting results? – TehShrike Feb 1 '12 at 22:08
  • 1
    Duplicate of codegolf.stackexchange.com/questions/3652/write-a-code-golfer/… ? I particularly like my BF answer there. – captncraig Feb 1 '12 at 23:06
  • 2
    I think ugoren makes a good point. Your winning criterion encourages competitors to do as little as possible since doing stuff costs characters -- characters which expand the program which is the input. Best to do as little as possible so the input is not very big in the first place. To put it another way - the best minifiers will be big, even after minification. Testing the shortest output will not show the best minifier. – Gareth Feb 2 '12 at 14:07
  • 1
    A better winning criterion might be to do the minification on the program as you've suggested, but to judge based on the difference between input and output. Although that might encourage competitors to stuff their programs with spaces and comments... – Gareth Feb 2 '12 at 14:10

Brainfuck

I always had in mind to do this, so here we go:

>>>,
[
[->+>+<<]             copy input

<<<+++++++++++++      fill cell with 13 for subtraction
[->+>+>+<<<]          and duplicate some times

>>>[->---<]           sub 39
>----                 43

[                     plus
-
[                     comma
-
[                     minus
-
[                     dot

<<[->>-<<]>>-         60

[                     less than
--
[                     greater than

<<<[->>>--<<<]>>>     86
---                   91

[                     open square bracket
--
[                     closing square bracket

>[-]                  remove copy

]]]]]]]]

>[.[-]>]              the copy has not been removed; print

<<[-]<<<[-]>[-]>      clean up

,]

Compresses itself to (144 characters):

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

The size of the compressing could probably still reduced a bit. The compression alogorithm itself is near perfect, it's hard to do any more than removing non-+-[]<>,..

  • Next easiest would be to compress any chain of +'s and -'s (or <>) to its equivalent. ex: ++-++ :: +++ or >>><< :: > – captncraig Feb 1 '12 at 23:10
  • @CMP your code example should never be in real application, so this should not produce any more compression. – copy Feb 1 '12 at 23:33
  • 1
    I have written code with that, mostly when I copy snippets from esolang that try to be independent of your memory layout. After substituting the proper number of pointer movements, sometimes such a situation can occur. Just a nice sanity check you could perform. – captncraig Feb 1 '12 at 23:38
  • +1 - Hooray for the Brainfuck answer! :-D does this minifier do anything besides removing whitespace and comments? – TehShrike Feb 2 '12 at 20:35
  • 1
    @TehShrike no, it simply preserves the 8 Brainfuck symbols. – copy Feb 3 '12 at 11:30

Python 2.6

I tried to implement a basic Python minifier which does the following things:

  1. Removing unnecessary whitespace
  2. Removing unnecessary newlines
  3. Removing comments
  4. Shortening variable names

As others stated, the winning criteria (shortest output when run through itself) does not make a whole lot of sense for a minifier if you want to start with a non-golfed solution.

Original (2591 characters)

# imports
import keyword, tokenize, sys, token, itertools

# vars
line = ""
out = [] ; block = []
idx = 0 ; previdx = 0 ; prevdirection = 0
varnames = set() ; imports = set()
t = tokenize.generate_tokens(sys.stdin.readline)
newline = '\n'

# settings
replace_varnames = True

while 1:
    try:
        toknum, tokval, _, _, _ = t.next()
        iskwd = tokval in keyword.kwlist

        if toknum == 1:
            if ''.join(line).lstrip().startswith("import "):
                imports.add(tokval)
            else:
                if not (iskwd or line[-1]=="." or tokval in __builtins__.__dict__.keys() or tokval.startswith("_") or len(tokval) <=2 or tokval in imports):
                    varnames.add(tokval)

        if toknum == 53:
            continue

        if toknum == 4:
            block.append(' '.join(x for x in ("".join(l for l in line if l)).strip().split()))
            line = []

        if toknum == 5 or toknum == 6:
            if out and out[-1] != newline:
                out.append(newline)

            previdx = idx
            idx += (-1,1)[toknum==5]

            indenting = previdx < idx
            dedenting = previdx > idx
            prefix = " " * previdx
            base = ";".join(l for l in block)
            if dedenting and prevdirection == 1:
                out.pop()
                out.append(base)
            else:
                if len(block) > 1:
                    if indenting:
                        out.append(prefix + ";".join(l for l in block[:-1]))
                        out.append(newline)
                        out.append("".join(prefix + l for l in block[-1:]))
                    else:
                        out.append(prefix + base)
                elif len(block) == 1:
                    out.append(prefix + base)

            block = []
            prevdirection = [-1,1][indenting]
        space_if_needed = [""," "][iskwd and tokval not in ("else","try")]
        line += space_if_needed + tokval + space_if_needed
    except StopIteration:
        out.append(" " * idx + ";".join(l for l in block))
        break

total_out=''.join(out)+newline
if replace_varnames:
    wrappers = [";"," ", ":", "=", "(", "[", ")", "]", ".",",","*","+","-","/","<",">","!","\n"]
    wrl = list(itertools.permutations(wrappers,2)) + zip(wrappers, wrappers)

    for i, e in enumerate(varnames):
        # print i, e, "v"+str(i)
        for p, a in wrl:
            total_out = total_out.replace(p+e+a,p+"v"+str(i)+a)

print total_out[0:-1]

Minified (by itself), 1388 characters (a 46 % reduction)

import keyword,tokenize,sys,token,itertools;v16="";v20=[];v19=[];v4=0;v15=0;v17=0;v10=set();v6=set();t=tokenize.generate_tokens(sys.stdin.readline);v8='\n';v13=True
while 1:
 try:
  v18,v9,_,_,_=t.next();v2=v9 in keyword.kwlist
  if v18==1:
   if ''.join(v16).lstrip().startswith("import "):v6.add(v9)
   else:
    if not (v2 or v16[-1]=="." or v9 in __builtins__.__dict__.keys() or v9.startswith("_") or len(v9)<=2 or v9 in v6):v10.add(v9)
  if v18==53:continue
  if v18==4:v19.append(' '.join(x for x in ("".join(l for l in v16 if l)).strip().split()));v16=[]
  if v18==5 or v18==6:
   if v20 and v20[-1]!=v8:v20.append(v8)
   v15=v4;v4+=(-1,1)[v18==5];v5=v15<v4;v12=v15>v4;v11=" "*v15;v14=";".join(l for l in v19)
   if v12 and v17==1:v20.pop();v20.append(v14)
   else:
    if len(v19)>1:
     if v5:v20.append(v11+";".join(l for l in v19[:-1]));v20.append(v8);v20.append("".join(v11+l for l in v19[-1:]))
     else:v20.append(v11+v14)
    elif len(v19)==1:v20.append(v11+v14)
   v19=[];v17=[-1,1][v5]
  v3=[""," "][v2 and v9 not in ("else","try")];v16+=v3+v9+v3
 except StopIteration:v20.append(" "*v4+";".join(l for l in v19));break
v1=''.join(v20)+v8
if v13:
 v0=[";"," ",":","=","(","[",")","]",".",",","*","+","-","/","<",">","!","\n"];v7=list(itertools.permutations(v0,2))+zip(v0,v0)
 for i,e in enumerate(v10):
  for p,a in v7:v1=v1.replace(p+e+a,p+"v"+str(i)+a)
print v1[0:-1]

Known limitations:

  1. Keyword arguments are problematic when variable name shortening is on.
  2. The code is ugly and can probably break some of the more dynamic Python constructs. It does work fine for itself (and other simple scripts) though.
  • I think your wrappers list is incomplete. What about \t and ^? And you can shorten names more using letters (or at least hex: "v%x"%i) – ugoren Feb 4 '12 at 17:41
  • Nice! This is sweet! – TehShrike Feb 4 '12 at 17:54
  • To minify the indentation, you can replace pairs of spaces by tab characters. See this answer: codegolf.stackexchange.com/questions/54/… – Eric Feb 6 '12 at 12:18

JavaScript, 562 → 334 bytes

40% reduction, but it's pretty compact to begin with.

Uses the Google Closure Compiler API in advanced optimization mode, which might rename certain variables but it is still functionally equivalent.

function minify(input) {
    // Closure Compiler API url:
    var url = 'http://closure-compiler.appspot.com/compile';

    // Create and initialize the Ajax request:
    var rq = new XMLHttpRequest();
    rq.open('POST', url, false);
    rq.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');

    // Send the request:
    rq.send('js_code=' + encodeURIComponent(input) +
        '&compilation_level=ADVANCED_OPTIMIZATIONS&output_format=text&output_info=compiled_code');

    // Return the result and just assume the code succeeded (hey, it's Google):
    return rq.responseText;
}

Here's a sample jsFiddle, and here's the code run on itself when compiled without a run:

window.a=function(c){var b=new XMLHttpRequest;b.open("POST","http://closure-compiler.appspot.com/compile",!1);b.setRequestHeader("Content-Type","application/x-www-form-urlencoded");b.send("js_code="+encodeURIComponent(c)+"&compilation_level=ADVANCED_OPTIMIZATIONS&output_format=text&output_info=compiled_code");return b.responseText};
  • 5
    lol. Clever! Though the code here doesn't do any minifying, just calls another minifyer :-P – TehShrike Feb 7 '12 at 20:10

APL, 76 character output

{(~(n ↓ b) ∧ ' ' ⍷ d) / d←(n←- +/ ∨\ ('⍝'⍷⍵) ∧ b←~a∨ ≠\ a←(('''' ⍷ ⍵) ∧ {⍵ ∧ ¯1⌽⍵}~'''''' ⍷ ⍵)) ↓ ⍵}⍞   ⍝ This takes a line of APL as input and removes comments and unnecessary spaces!

This isn't really a serious attempt at golfing, but it was interesting to implement.. I think? It removes comments (⍝) and spaces. This works on Dyalog APL. (Note that it preserves spaces and ⍝ characters found in quoted strings.)

Example (first line is the code, second is the input, third is the output):

      {(~(n ↓ b) ∧ ' ' ⍷ d) / d←(n←- +/ ∨\ ('⍝'⍷⍵) ∧ b←~a∨ ≠\ a←(('''' ⍷ ⍵) ∧ {⍵ ∧ ¯1⌽⍵}~'''''' ⍷ ⍵)) ↓ ⍵}⍞   ⍝ This takes a line of APL as input and removes comments and unnecessary whitespace!
{(~(n ↓ b) ∧ ' ' ⍷ d) / d←(n←- +/ ∨\ ('⍝'⍷⍵) ∧ b←~a∨ ≠\ a←(('''' ⍷ ⍵) ∧ {⍵ ∧ ¯1⌽⍵}~'''''' ⍷ ⍵)) ↓ ⍵}⍞   ⍝ This takes a line of APL as input and removes comments and unnecessary whitespace!
{(~(n↓b)∧' '⍷d)/d←(n←-+/∨\('⍝'⍷⍵)∧b←~a∨≠\a←((''''⍷⍵)∧{⍵∧¯1⌽⍵}~''''''⍷⍵))↓⍵}⍞

Bash, 17 characters output

They say my Python solution below isn't a minimizer. So here's one that does minimize, and it's even shorter:

 sed -e '1 s/^ //'

It reduces its own size from 18 to 17 characters.

Python, 38 character output

I misunderstood the question a bit, so my 0 character attempt was incorrect.
So I have a new and improved solution. I'd start a new answer, but since it's in the same spirit, I prefer to edit.
I'm sure it can be much improved, especially if you switch the language. But as it is, I'm still way ahead of the competition:

import sys
for l in sys.stdin:print l,

When given itself as input, you get a 100% equivalent program, 38 characters long.


Python, 0 character output (but not when run on itself, so it doesn't count).

import sys
program = [ l for l in sys.stdin ]
if len(program)>1 or program[0]!="x=12345\n":
    print "".join(program),

Sample input (also Python):

x=12345

I admit, it isn't very effective on most programs.
However, it reduces my example to 0 chars, still keeping it equivalent.

"The shortest output wins" - I don't think anyone can top this.

  • 2
    But what is the output when you feed your program code in (instead of feeding in arbitrary example code)? – TehShrike Feb 1 '12 at 22:08
  • 2
    Also, even ignoring the fact that the input was incorrect, the output given there is not functionally equivalent to the input. – TehShrike Feb 1 '12 at 22:13
  • 6
    I think by "output", he means "output given your minimizer program as input", not "shortest output given arbitrary input". – Keith Randall Feb 2 '12 at 0:21
  • 3
    It's not a minimiser, though. The spec is rather loose, but this is stretching it beyond breaking point. – Peter Taylor Feb 2 '12 at 9:14
  • 1
    Your bash solution can break scripts that contain strings which span multiple lines. – hammar Feb 2 '12 at 13:09

Your Answer

By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

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