11
\$\begingroup\$

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
\$\endgroup\$
8
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Feb 1, 2012 at 20:47
  • \$\begingroup\$ @PeterTaylor I agree. Do you have any suggestions to help resolve that tension and encourage people to submit interesting results? \$\endgroup\$
    – TehShrike
    Commented Feb 1, 2012 at 22:08
  • 1
    \$\begingroup\$ Duplicate of codegolf.stackexchange.com/questions/3652/write-a-code-golfer/… ? I particularly like my BF answer there. \$\endgroup\$
    – captncraig
    Commented Feb 1, 2012 at 23:06
  • 2
    \$\begingroup\$ 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. \$\endgroup\$
    – Gareth
    Commented Feb 2, 2012 at 14:07
  • 1
    \$\begingroup\$ 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... \$\endgroup\$
    – Gareth
    Commented Feb 2, 2012 at 14:10

6 Answers 6

14
\$\begingroup\$

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-+-[]<>,..

\$\endgroup\$
5
  • \$\begingroup\$ Next easiest would be to compress any chain of +'s and -'s (or <>) to its equivalent. ex: ++-++ :: +++ or >>><< :: > \$\endgroup\$
    – captncraig
    Commented Feb 1, 2012 at 23:10
  • \$\begingroup\$ @CMP your code example should never be in real application, so this should not produce any more compression. \$\endgroup\$
    – copy
    Commented Feb 1, 2012 at 23:33
  • 1
    \$\begingroup\$ 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. \$\endgroup\$
    – captncraig
    Commented Feb 1, 2012 at 23:38
  • 1
    \$\begingroup\$ @TehShrike no, it simply preserves the 8 Brainfuck symbols. \$\endgroup\$
    – copy
    Commented Feb 3, 2012 at 11:30
  • \$\begingroup\$ Dang it, that was my idea as soon as I read the question! :( \$\endgroup\$
    – Ry-
    Commented Feb 7, 2012 at 5:16
6
\$\begingroup\$

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.
\$\endgroup\$
3
  • \$\begingroup\$ 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) \$\endgroup\$
    – ugoren
    Commented Feb 4, 2012 at 17:41
  • \$\begingroup\$ Nice! This is sweet! \$\endgroup\$
    – TehShrike
    Commented Feb 4, 2012 at 17:54
  • \$\begingroup\$ To minify the indentation, you can replace pairs of spaces by tab characters. See this answer: codegolf.stackexchange.com/questions/54/… \$\endgroup\$
    – Eric
    Commented Feb 6, 2012 at 12:18
4
\$\begingroup\$

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};
\$\endgroup\$
1
  • 5
    \$\begingroup\$ lol. Clever! Though the code here doesn't do any minifying, just calls another minifyer :-P \$\endgroup\$
    – TehShrike
    Commented Feb 7, 2012 at 20:10
2
\$\begingroup\$

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⌽⍵}~''''''⍷⍵))↓⍵}⍞
\$\endgroup\$
1
\$\begingroup\$

APL (Dyalog APL), 2145 → 763 bytes (−64%)

 Minify←{
     ⍝ Minifies source code ⍵ — see examples at end
     ns←⎕NS ⍬  ⍝ namespace to keep names created by side-effects
     
     src←ns.(61 ⎕ATX ⎕FX) '_',⊆⍵  ⍝ get normalised source
     ⍝ make large magnitude numbers use exponential format:
     src←'\d+\.\d+' '\d00+'⎕R{⍵.PatternNum: (⊃,'E',∘⍕¯1+≢)⍵.Match ⋄ ⍵.Match} src

     (from to)←⍬ ⍬
     (from to),∘⊂←'''[^'']+''|\d+ [eEjJ]'       '&'      ⍝ preserve strings and space before e/j
     (from to),∘⊂←' ?⍬ ?'                       '⍬'      ⍝ remove spaces around ⍬
     (from to),∘⊂←'([^#]) # ([^#])'             '\1#\2'  ⍝ remove spaces around #
     (from to),∘⊂←'# ([^#])'                    '#\1'    ⍝ remove space after #
     (from to),∘⊂←'([^#]) #'                    '\1#'    ⍝ remove space before #
     (from to),∘⊂←' ([⎕:])'                     '\1'     ⍝ remove space before ⎕ and :
     (from to),∘⊂←' *⍝.*|^ +'                   ''       ⍝ remove comments and leading spaces
     (from to),∘⊂←'(\pL) 0\.'                   '\1.'    ⍝ make X 0.5 into X.5
     (from to),∘⊂←'(\d) (\pL)'                  '\1\2'   ⍝ make 1 X into 1X
     (from to),∘⊂←' ?(⋄ ?)+'                    '⋄'      ⍝ make 1 X into 1X
     (from to),∘⊂←'(:End)([DFHPRTW]|If|Sel)\w+' '\1'     ⍝ make :EndXYZ into :End where permitted
     (from to),∘⊂←':Return|:GoTo '              '→'      ⍝ Replace keywords with "→"

     src←from ⎕R to ⊢ src  ⍝ do it!

     ⍝ make small magnitude numbers use exponential format:
     src←'\b0\.00+(\d+)'⎕R{lead←-/⍵.Lengths ⋄ mantissa,'E',⍕10⍟⍵.Match÷⍥⍎mantissa←lead↓⍵.Match} src

     src←'\b0\.'⎕R'.' ⊢ src  ⍝ make 0.5 into .5

     _←'adic' ns.⎕CY 'dfns'  ⍝ import adic from dfns.dws
     from←' '(2⌽'\b\b',~)¨⍨' '~¨⍨↓ns.(⎕REFS ⎕FX)'\.[∆⍙\w]+'⎕R' '⍠'UCP' 1 ⊢ src  ⍝ get non-dotted names
     from{⍵,⍺,⍵}¨←⊂'\b'                ⍝ names have to be in isolation
     to←('_',⎕A,⎕C ⎕A)∘ns.adic¨⍳≢from  ⍝ generate short names
     (from to),⍨∘⊂←'''[^'']+''' '&'    ⍝ preserve strings
     from ⎕R to ⊢ 1↓src~⊂''            ⍝ minify names and remove blank lines

     More examples:
         Refs: ⎕SE # ## ⎕DMX #
         Numbers: 100000 0.000001 16 E3 16 F3
 }

Run it on its own source to get:

B←{
K←⎕NS⍬L←K.(61⎕ATX⎕FX)'_',⊆⍵
L←'\d+\.\d+' '\d00+'⎕R{⍵.PatternNum:(⊃,'E',∘⍕¯1+≢)⍵.Match⋄⍵.Match}L
(H M)←⍬⍬
(H M),∘⊂←'''[^'']+''|\d+ [eEjJ]' '&'
(H M),∘⊂←' ?⍬ ?' '⍬'
(H M),∘⊂←'([^#]) # ([^#])' '\1#\2'
(H M),∘⊂←'# ([^#])' '#\1'
(H M),∘⊂←'([^#]) #' '\1#'
(H M),∘⊂←' ([⎕:])' '\1'
(H M),∘⊂←' *⍝.*|^ +' ''
(H M),∘⊂←'(\pL) 0\.' '\1.'
(H M),∘⊂←'(\d) (\pL)' '\1\2'
(H M),∘⊂←' ?(⋄ ?)+' '⋄'
(H M),∘⊂←'(:End)([DFHPRTW]|If|Sel)\w+' '\1'
(H M),∘⊂←':Return|:GoTo ' '→'
L←H⎕R M⊢L
L←'\b0\.00+(\d+)'⎕R{I←-/⍵.Lengths⋄J,'E',⍕10⍟⍵.Match÷⍥⍎J←I↓⍵.Match}L
L←'\b0\.'⎕R'.'⊢L
F←'adic'K.⎕CY'dfns'
H←' '(2⌽'\b\b',~)¨⍨' '~¨⍨↓K.(⎕REFS⎕FX)'\.[∆⍙\w]+'⎕R' '⍠'UCP' 1⊢L
H{⍵,⍺,⍵}¨←⊂'\b'
M←('_',⎕A,⎕C⎕A)∘K.adic¨⍳≢H
(H M),⍨∘⊂←'''[^'']+''' '&'
H⎕R M⊢1↓L~⊂''
C G:
E:⎕SE# ##⎕DMX#
D:1E5 1E¯6 16 _ 16F3
}

Run this on itself to get something very similar (only names differ).

Removes:

  • All redundant spaces
  • Comments
  • Empty lines
  • Leading and trailing zeros in numbers

Abbreviates:

  • Numbers into exponential form (only when it actually saves characters)
  • Control structure keywords to their shorter alternate forms
  • All used names into _, A, B, C,… Z, a,… z, __, _A, _B,…

Fails on:

  • Evaluated strings
  • Global and to-be-imported names (abbreviates them)
\$\endgroup\$
-1
\$\begingroup\$

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.

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

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.