Programming Puzzles & Code Golf Stack Exchange is a question and answer site for programming puzzle enthusiasts and code golfers. It's 100% free, no registration required.

Sign up
Here's how it works:
  1. Anybody can ask a question
  2. Anybody can answer
  3. The best answers are voted up and rise to the top

Write the shortest program that prints the entire lyrics of "Never Gonna Give You Up" by Rick Astley.


  • Must output the lyrics exactly as they appear in the above pastebin. Here's the raw dump:
  • Cannot rely on any external resources - all lyrics must be generated by / embedded in code.
  • No use of existing compression algorithms (e.g. gzip / bzip2) unless you include the full algorithm in your code.
  • Use any language, shortest code wins.

Update, 1st June 2012:
For solutions containing non-ASCII text, the size of your solution will be counted in bytes, based on UTF-8 encoding. If you use codepoints that cannot be encoded in UTF-8, your solution will not be judged as valid.

Update, 7th June 2012:
Thank you all for your awesome solutions! I'll be accepting the shortest answer tomorrow afternoon. Right now, Ed H.'s 554-character solution is winning, so get cracking on some improvements if you want to beat him! :)

share|improve this question
@w0lf You're correct. I only scanned the lyrics to verify their accuracy, I must've missed that one. I'll accept either "know" or "known". – Polynomial May 29 '12 at 7:30
26k+ views in just 3 days. WOW... – Gaffi Jun 1 '12 at 21:30
How in the world did this generate so many views and votes? Whatever he did, I'm going to reverse engineer it. – PhiNotPi Jun 2 '12 at 21:27
@PhiNotPi Good luck reverse engineering "Jeff Atwood tweeting you". – breadbox Jun 3 '12 at 8:04
+1 for mass rickrolling CodeGolf SE – Optimus Jun 3 '12 at 21:43

54 Answers 54

up vote 52 down vote accepted

Ruby 576 557 556 (552) chars && PHP 543 chars

Another search-and-replace solution. Note that this form of solution is essentially a Grammar-based Compression Code Check out for a simple to understand compression example.

I've written the substitution rules so that the starting character for each substitution is computed (they are in sequential ASCII order); it need not be present in the transition data.

s="We; n7trangMsL8loT63Ke rules5s8d8I
AJull commit4nt'sChatFKink: of6CHldn'tRetKisJrom<ny@Ruy-/A= if?<sk 42DS'tLE 4?;Lo8bli=L7ee..
I justCannaLE?2Gotta >u=Msta=.|
Ng1Nlet? downNrun<rH=5desMt?N>cryNsayRoodbyeNtE< lie5hurt?|

We'T3n each@Jor s8lSg6r hear9<ch: but6;Lo7hyL7BInsideCe both3Cha9Ro: S
We3KeRa45we;QplB|1)O)NgiT, nPgiT
(G|iT? up| howFJeel:
| know|me|<= |
YH|8s|o |t's been|ing|'re| a|nd|make? | yH| othM|A|ay it
| w|D|ell| I'm|G|ou|I| f|Lh| t|er|
(Ooh|eTrQ|RSna | g|on|ve".scan(/[^|]+/){s.gsub!((i+=1).chr,$&)}
puts s

implementation notes

  • The above solution is 556 chars long, but scores 552 with the removal of newlines from source code. It is slightly better scoring than the original 556 char solution I had that scored 554.
  • I use "known" instead of "know"; this makes the verse repetition identical and should make the compression better
  • I originally optimized the rules by repeatedly searching for the substitution that would yield the most decrease in current code size. However, I found that RiderOfGiraffe's substitution rules were (slightly) better than mine, so I'm now using a modified version of his rules.
  • I spent time reordering the rules so that a single substitution pass can decompress everything.
  • Due to the computed initial character in the substitution rules, my rule list has a rule for every character in a contiguous ASCII range. I found that having some "dummy" substitution rules was better for code size than having all real rules and coding a final text fixup substitution.
  • This example is small enough that you probably could successfully write a program to find the optimal (least total cost) set of rules. I would not expect doing this would yield size reductions of more than a few bytes.

older implementation

This older implementation has 576 characters and started with substitution rules from ugoren's bash/sed implementation. Ignoring the substitution variable renaming, my first 28 substitutions are exactly the same as those performed in ugoren's program. I added a few more to lower the overall byte count. This is possible because my rules are more efficiently represented than those in ugoren's implementation.

puts"WeM noHtraLersB loJ;6 C rules=so do $
& full commitment'sGhat<thinkDof;Gouldn'tKet this fromFny oCrKuy.
-&E if9ask me1~on't @ me:MBo bliEBHee//

We'J6n each oCr forHo loL;r hear2FchDbut;MBoHhyBH7$nsideGe both6Gha2ADon
We6 CKame=weM>pl7|
$ justGanna @:1#otta 8uEerstaE/|
5?9up5let9down5runFrouE=desert:58cry5sayAodbye5@F lie=hurt:|(Ooh)5?, nI>?
(#4| how<feeliL
|t's been|(Ooh,K4|iJ9up)
NI>| know|ay it
|make9|: | you|
You| $'m |FE |Anna |giJ|tell|Ko| to|the|iL |nd| a| w| s|eJr|ve| g|ng|'re".split("|").inject{|m,o|m.gsub((i+=1).chr,o)}.tr('&~#$',"ADGI")

I did not try optimizing the substitution rules in this one.

contest notes

The search-and-replace decompression scheme works well for this contest because most languages have pre-built routines that can do this. With such a small amount of text to be generated, complex decompression schemes do not seem to be feasible winners.

I've used only ASCII text and also avoided using unprintable ASCII characters. With these restrictions, each character in your code can only represent up to a maximum of 6.6 bits of information; this is very different than to real compression techniques where you use all 8 bits. In some sense, it is not "fair" to compare to gzip/bzip2 code size because those algorithms will use all 8 bits. A fancier decompression algorithm may be possible if you can include traditionally unprintable ASCII in your strings AND each unprintable character is still written in your code as a single byte.

PHP Solution

I justCannaLE?2Gotta >u=Msta=.q
Ng1Nlet? downNrun<rH=5desMt?N>cryNsayRoodbyeNtE< lie5hurt?q

We'T3n each@Jor s8lSg6r hear9<ch: but6;Lo7hyL7BInsideCe both3Cha9Ro: S
We3KeRa45we;QplBq1)O)NgiT, nPgiT
(GqiT? upq howFJeel:
q knowqmeq<= q
YHq8sqo qt's beenqingq'req aqndqmake? q yHq othMqAqay it
q wqDqellq I'mqGqouqIq fqLhq tqerq
(OohqeTrQqRSna q gqonqve"),"We; n7trangMsL8loT63Ke rules5s8d8I
AJull commit4nt'sChatFKink: of6CHldn'tRetKisJrom<ny@Ruy-/A= if?<sk 42DS'tLE 4?;Lo8bli=L7ee..

The above solution takes the PHP from "a sad dude" and combines it with my substitution rules. The PHP answer turns out to have the shortest decompression code. See

share|improve this answer
My sed solution surely can't beat it. I'm working on something that hopefully has a chance - you have 75 bytes of overhead, maybe I'll cut it down (not in Ruby). – ugoren Jun 3 '12 at 19:50
I find 555 bytes in your program (with no trailing newline). – r.e.s. Jun 5 '12 at 1:03
The OP's scoring method is to exclude newline separators in ascii source code, so it seems this should score 554. – r.e.s. Jun 7 '12 at 12:35
Great solution! :) – Polynomial Jun 8 '12 at 15:55
Looks like the shortest decompression code was in PHP. Here's a 543 byte solution (my rules, using code snippets from "a sad dude") – Ed H. Jun 9 '12 at 17:38

Python 2.7, 285 characters

import md5,sha,random
C="\n ')(,ADGIONWYacbedgfihkjmlonpsrutwvy"
while != M or != S:
  t="".join([random.choice(C) for _ in xrange(L)])
print t,

Generates random 1870-char-long strings of the appropriate alphabet until the MD5 and SHA sums of the string match those of the lyrics to the Rick Astley song "Never Gonna Give You Up".

If unclear that this works, replace line #3 "L,M,S=..." with this one:


Run the program and see it output "AAA", since AAA is 3 chars long, has an MD5 sum of e1faffb3e614e6c2fba74296962386b7 and an SHA1 sum of 606ec6e9bd8a8ff2ad14e5fade3f264471e82251.


Trade runtime for code size, since execution time limit is unspecified and success is defined strictly by correctness and code size.

Potential problems

  1. Execution time of non-deterministic solution likely exceeds Heat Death of the Universe.
  2. Problem specifies that code must be self-contained, it is assumed that importing standard hash functions (md5,sha) are acceptable. Inlining the code for these algorithims would clearly make the programs very large, though if necessary we could switch to some sort of custom hash function.

Potential improvements

  1. We can reduce the code size even further by omitting the MD5-related data and relying only on SHA; though it's unclear if this would always lead the correct answer.
  2. Speed up our program by including character counts so we're just shuffling permutations, like so:

Python 2.7, 410 chars (the long string-building literal can be condensed)

import array as a,sha,random
 "\n"*306+"e"*197+"o"*162+"n"*162+"a"*109+"r"*91+"u"*84+"g"*77+" "*73+
while != S:
print t.tostring(),


  1. install popular PHP web application and wait for file to be hacked and output file modified into lyrics by Rick Astley hacker fanboys
  2. import rickrolling as r; print r.lyrics
  3. integrate lyrics into next POSIX standard as const char * rckrll_txt_r(void)
  4. integrate lyric opcodes into next generation x86 instruction set expansion
  5. Set up voice-to-text software, call Rick Astley and have him sing into the phone. Sometimes the manual solutions are the best ones.
share|improve this answer
We had the same idea. :) It'd probably be more efficient to use a LargeInt package to just COUNT until you hit a string with right number of characters, hashing to the right amount. In fact, with a good seed based on some number that's easily expressible, we could probably speed it up so it finishes before the universe dies of heat death! – Matt Cruikshank Jun 1 '12 at 20:51
As they teach you while programming: first make sure it runs correct, and only after that worry about performance ;-) @Matt: The "performance advice" sort of made me laugh. It was like telling a snail to keep its head low for better earodynamics :) – Wouter van Nifterick Jun 1 '12 at 21:24
PEP21: import rickrolling: this might be approved before actual Heat Death of the Universe ;-) – dschulz Jun 2 '12 at 1:23
The chance that this produces the correct output is vanishingly small: there are a vast number of other strings with the same hash, and you will almost certainly find one of them first. – Geoffrey Irving Jun 2 '12 at 5:19
The (pseudo) random number generator you are using is cyclic, so it's unlikely to ever produce the correct result. – gnibbler Jun 6 '12 at 12:02

Bash / Sed, 705 650 588 582 chars

The basic idea is simple replacement. Instead of writing, for example, Never gonna give you up\nNever gonna let you down, I write Xgive you up\nXlet you down and replace all X with Never gonna.
This is achieved by running sed with a set of rules, in the form s/X/Never gonna /g.
Replacements can be nested. For example, Never gonna is common, but so is gonna in other contexts. So I can use two rules: s/Y/ gonna/g and s/X/NeverY/g.
When adding rules, parts of the song texts are replaced by single characters, so it gets shorter. The rules become longer, but if the string replaced is long and frequent, it's worth it.
The next step is to remove repetition from the sed commands themselves. The sequence s/X/something/g is quite repetitive.
To make it shorter, I change sed commands to look like Xsomething. Then I use sed to convert this into a normal sed command. The code sed 's#.#s/&/#;s#$#/g;# does it.
The final result is a sed command, whose arguments are generated by another sed command, in back-quotes.
You can find a more detailed explanation in this link.


sed "`sed 's#.#s/&/#;s#$#/g#'<<Q
LMWe'veKn each o!r for-o longPr hearHzchJbutP're2o-hy2-@Insidexe bothKxhaHCJonMWeK ! game+we'reZpl@
TMI justxanna _UFGotta QuXerstaXR
RM~Squp~letqdown~runzrouX+desertU~Qcry~sayCodbye~_z lie+hurtU
E(Ooh)~S, neverZSM(GV
F how=feelingM
Ht's been
%(Ooh, gV
K know
@ay itM
U you
= I'm 
C go
2 to
z a
x w
- s
We're no-trangers2 lovePK ! rules+so do I
A full commitment'sxhat=thinkJofPxouldn't get this fromzny o!r guyT
LAX ifqask meFDon't _ meU're2o bliX2-eeRRMM%%EELTRR

The decompression engine is just 40 characters long. The other 543 are the translation table and compressed text. bzip2 compresses the song to 500 bytes (without the engine, of course), so there must be room for improvement (though I don't see how I'd add Huffman encoding or something like this cheap enough).
<<Q (or <<_) is used to read until a given character. But the end of script (or backquote expression) is good enough. This sometimes causes a warning.

Older and simpler solution, 666 chars:

sed "
s/L/ MWe'veKn each other for so longMYour heart's been aching butMYou're too shy to say itMInside we bothK what's been going onMWeK the game+we'reZplay itM/;
s/T/MI just wanna tellU how I'm feelingMGotta makeU understandR/;
s/R/M ~giveU up~letU down~run around+desertU~makeU cry~say goodbye~tell a lie+hurtU/g;
s/E/(Ooh)~give, neverZgiveM(GV/g;
s/V/iveU up)M/g;
s/U/ you/g;
s/+/ and /g;
s/K/ know/g;
s/Z/ gonna /g;
We're no strangers to love
YouK the rules+so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guyT
LAnd ifU ask me how I'm feeling
Don't tell meU're too blind to seeRRM M(Ooh, gV(Ooh, gVEEL TRR
share|improve this answer
This is great, but your output contains an extra space character on the blank lines. That may be an issue with the cut-and-paste from the pastebin? Also, you should be able to save a byte by replacing the \0 with &. – breadbox May 30 '12 at 16:11
This is both the shortest so far, and my favourite. I was wondering how long it'd be before someone came up with a sed solution :) – Polynomial May 30 '12 at 17:21
@breadbox, Cut&paste indeed brought this space. I removed it, saved 4 chars. With your & it makes 5. – ugoren May 31 '12 at 6:41
@r.e.s., added a better explanation. – ugoren Jun 3 '12 at 13:40
Thanks, esp for the "more detailed explanation" link ;o)))) – r.e.s. Jun 3 '12 at 13:46

Whitespace - 33115 characters

StackExchange chomped my answer, here is the source:

Not great... I think I may be able to shrink it down a bit though.

(If you don't know what Whitespace is:

share|improve this answer
I'm guessing you used some tool to compile this into whitespace? – evilcandybag Jun 2 '12 at 9:13
+1 for creating the longest solution I've ever seen on CodeGolf. – Polynomial Jun 3 '12 at 11:27
(+1) I can get this down to 27,493 bytes (or to 25,623 bytes if using one of the less-stringent WS interpreters) just by using direct character-by-character processing -- essentially 1,943 "push-and-output" statements, with no jumps, no loops, no subroutines, no storing/retrieving values for reuse -- so the length can still be drastically reduced. – r.e.s. Jun 3 '12 at 16:56
@CalebJares A programming language that uses three instructions: tab, space and newline. – Camilo Martin Dec 18 '12 at 14:04
link to source died... – Xantix Feb 21 '14 at 8:46

JavaScript, 590 588 bytes

f="WeP nTstrangersZTloMX^Zhe rules[sTdTIqA fuFcommitment'sEhat I'mZhinkQofXEouldn'tJetZhis from anyRguy{Sn}AH ifCask me_Don'tZeFmexPZoTbliHZTsee~~qU,J`)U,Jzz`)S}{~~~q|g`|letCdown|run arouH[desertx|Lcry|sayJoodbye|teFa lie[hurtx} eachRfor sTlongXr hearVachQbutXPZoTshyZTsKInsideEe both^EhaVgoQonqWe^ZheJame[weP]plK|qNeMr]{qI justEannaZellx_Gotta LuHerstaH~z`)U)|giM, neMr]giMq(GCyouq\n`iMCup_ how I'm feelingq^ know]Jonna [ aH Z tXqYouVt's been Uq(OohTo SqqWe'M^R other Qing P'reMveLmakeCKay itqJ gHndFll E wCx ";for(i in g="CEFHJKLMPQRSTUVXZ[]^_`qxz{|}~")e=f.split(g[i]),f=e.join(e.pop())

Depending slightly on the way the string is "printed".

share|improve this answer
Really brilliant. – Jim Blackler Jun 1 '12 at 21:10
The for in loop can break it in consoles due to functions as extra members of g. Add if(g.indexOf(g[i])!=-1) before e= to fix it. – Tesserex Jun 2 '12 at 14:53
Very nice. Though it doesn't actually print the song (at least when tested with – ugoren Jun 3 '12 at 7:05
Almost the shortest... Trying to calculate compression: For N repetitions of length L, you use L+N+2 characters (I think) - as good as my solution, a bit worse than Rex Kerr's. You have 54 bytes additional overhead, while I have only 40. The gap between us is 7 bytes only, which probably means you found compression opportunities I missed. – ugoren Jun 3 '12 at 7:10
I don't even have the slightest idea on how this works... Can we get an explanation? – Spedwards Jun 18 '14 at 11:57

C# 879 816 789 characters

First attempt at CodeGolf so definitely not a winner, pretty sure it is valid despite it's nastiness.

string e="N£give, n£give",f="(Give ! up)",g="(Ooh)",h=@"I just wanna tell ! how I'm feeling
Gotta make ! understand",i="(Ooh, give ! up)",j="N£",k=@"We've known each other for so long
Your heart's been aching but
You're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it",l="",a=String.Join("\n"+j,l,"give ! up","let ! down","run around and desert !","make ! cry","say goodbye","tell a lie and hurt !"),b=String.Join("\n",@"We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy",h,a,l,k,@"And if ! ask me how I'm feeling
Don't tell me !'re too blind to see",a,a,l,i,i,g,e,f,g,e,f,l,k,l,h,a,a,a).Replace("!","you").Replace("£","ever gonna ");
share|improve this answer
+1 "definitely not a winner" -> just because you use C# you're already a winner to me. It's a great language! :) BTW: you can save off some chars by removing the spaces surrounding the equal signs. – w0lf May 30 '12 at 12:56
Also: instead of something like var s1="a";var s2="b"; try using string s1="a",s2="b"; if you have 2+ declarations it's shorter. – w0lf May 30 '12 at 13:01
Thanks for the comments w0lf, now reduced by 63 chars! – hermiod May 30 '12 at 19:02
+1 for trying codegolf for the first time =) – Conrad Meyer Jun 1 '12 at 17:16

Scala, 613 bytes

print(("""We'r%e %n&o &strangers9 t&9lo#ve#4
You47 know7 [th[%rules0 aZndZ 0s&d&I
A full commitment's what1 I'm 1[ink=ing= of4 wouldn't get [is from any! o[er !guy>
I just wanna <tell<]- ]you-3 how1feel=
3Gotta _make]_uZerstaZ@>
Ne#r$./$ gonna /g2i#]up2$let]down$run arouZ0desert-$_cry$say goodbye$< a lie0hurt-@?

We'#7n each!for s&long4r hear;t's been ;ach= but4:'r%to&:shy9say8 it
8Insid%w%bo[7 wha;going on
We7 [%game0we're/play8?AZ if]ask me3Don't < me-:bliZ9see@@

5(Ooh, g2)
556(Ooh)$gi#, ne#r/gi#^
"""/:("!#$%&Z[]^_"++('-'to'@'))){(x,c)=>val y=x.split(c);y(0)+y(1)+y.drop(2).mkString(y(1))}

This is a text decompression algorithm, recursively applying the rule that ~stuff~ blah ~ ~ should be converted to stuff blah stuff stuff (i.e. the first time you see an unfamiliar symbol pair, it delimits what to copy; thereafter, you fill in the value when you see it).

Note: there may be an extra carriage return at the end, depending on how you count. If this is not permissible, you can drop the last one on the quote (saving one character) and change the split to split(" ",-1) (spending 3 characters), for 615 bytes.

share|improve this answer
Very nice. The compression is slightly better than my solution - for N repetitions of length L, you use L+N+1 chars, while I use L+N+2. But your decompression code is 102 characters, while mine is 40. – ugoren May 31 '12 at 20:25
@ugoren - Indeed; Scala is too verbose in that regard, and there are not >62 productive substitutions to make. Still, I thought this was an interesting approach (as was yours). – Rex Kerr May 31 '12 at 20:48
+1 for a recursive solution. – Adam Fraser Jun 1 '12 at 12:34
Interesting, but doesn't work for me in the REPL. Need's another round closing parens in the end; but then it works. – user unknown Jun 10 '12 at 16:42

Python, 597 589 bytes

It may be possible to squeeze out another couple of bytes:

d="""ellU wTay it
S otherRConna Qmake4 PveMndL aK'reJingHt's beenFo E gC
Youz txKL q know9
N28 how I'm feelH
7iM4 up66)B)8giM, n2giM
(G5 you4
I justTannaxU47Gotta PuLerstaL03eMrQ2

We'M9n eachR for sElongzr hearFKchH butzJxoEshyxEsSInsideTe both9ThaFCoH on
8g68let4 down8runKrouLqdesert48Pcry8sayCoodbye8tUK lieqhurt40WeJ nEstrangersxEloMz9xhe rulesqsEdEI
A full commitment'sThat I'mxhinkH ofzTouldn'tCetxhis fromKnyRCuy31AL if4Ksk me7Don'txU me4JxoEbliLxEsee00
for s in'UTSRQPMLKJHFECBzxq9876543210':a,b=d.split(s,1);d=b.replace(s,a)
print d
share|improve this answer
(+1) Very nice. BTW, I find 588 bytes with no trailing newline. – r.e.s. Jun 4 '12 at 4:21

Perl, 724 714 883 bytes

So the change to the rules penalizing the use of Latin-1 kind of killed my solution. It's a different enough approach that I hate to just delete it, though, so here's a restricted version that only uses 7-bit ASCII, as per the new rules, at a huge increase in size.

sub d{($d=$d{$_})?d(@$d):print for@_}sub r{%d=map{chr,($d=pop)&&[$d,pop]}0..96,113..127;&d}r"We^P nEstraKersPElo~^_-SP< 5lesMsEdEI
A3ull commitment's#hat I'mPhink9 of^_#}ldn't^?/Phis3romVny %<r^?uy
I just#azP6UhS I'm3eH9
G%ta +JTerstaT^HX5^D    1^@^U^F^CXt^E^Y^X

We'~-; each %<r3or sEloK^_r <aL's=ee8ch9=ut^_^PPoEshyPE& it
Insi.#e=%h-S#hat's=een|9 on
We-SP<^?am7we^Px pl? it
ARif]Vsk me hS I'm3eH9
Don'tP6 me]^PPoEb*RtE1e^HX5^D   1^@^U^F^CXt^E^Y^X^HX5^D 1^@^U^F^CXt^E^Y^X

(Ooh)4, n^F
(Ooh)4, n^F

We'~-; each %<r3or sEloK^_r <aL's=ee8ch9=ut^_^PPoEshyPE& it
Insi.#e=%h-S#hat's=een|9 on
We-SP<^?am7we^Px pl? it

I just#azP6UhS I'm3eH9
G%ta +JTerstaT^HX5^D    1^@^U^F^CXt^E^Y^X^HX5^D 1^@^U^F^CXt^E^Y^X^HX5^D 1^@^U^F^CXt^E^Y^X
g evuoo^?nna{0z|000000xry q~_e}`0^N[
0 Z0a  ]dnwo T~it 00RVtrgnuU0le0Q^? o0]LpJ00yaamb ehnSekKiVnMelHurFZf k es0teedn20:>il000?sto0w 0}Y0!
U^^e^V^QC^W/X^R;^N^Ll0.^S^K^MV6^To ^G^\8ey^]r^Bc^A^O"=~/./gs

Of course the control characters are still mangled here, so you'll still want to use the base64-encoding:


Because I think it should still be visible despite being DQ'd, here's the original solution:

sub d{($d=$d{$_})?d(@$d):print for@_}sub r{%d=map{chr,[pop,pop]}45..63,122..255;&d}r" ¯:ç5raâ08/þ; Ölesì|dçI
AÌull comm°6ntŒ3èhink1fÍýldn'tÿÙèhisÌromðny4ÿuy ju5Íaú=î9GÐ Ëäï0ï
'þœn ea}4Ìo|/â-aêÔ}ÜÚut.shy8ÎnsiÞÍeÚÐhœ3nü1n;ÿamÓwe¯ù plá
Aíifôðsk 6 9Don't= 6ô.bÕítçÏe
'þœ ea}4Ìo|/â-aêÔ}ÜÚut.shy8ÎnsiÞÍeÚÐhœ3nü1n;ÿamÓwe¯ù plá
 ju5Íaú=î9GÐ Ëäï0ï
g evuooÿnnaûúürþýyøeùö÷ N
õó òa dn ô ïíðwotrþit  oleôêuîéÿgnyalæpäedkaâiãòb teënØkurilðnìeeheÝtoesásw f ÑmÖñY
r'bdhÓÏÞÕ tñìïñåîÙëdÎñ× s'oüyrÁÅeyÄð(

° I )ßee¶ rhm'Úat 
oèÜæçŒrÒÐtaÒèëo hcçseÌ
hz{àèreœn >?çèhÍemts 7~Üs<ol¯Ò"=~/./gs

The base64-encoding of the script:

share|improve this answer
Your character count appears to be 644, not 724. Am I missing something? – w0lf May 29 '12 at 7:36
@w0lf It's 724 bytes, which translates to 644 UTF-8 characters. I'm not sure which one counts, really. – Polynomial May 29 '12 at 8:29
@Polynomial I don't know either, but hopefully someone will know and enlighten us. – w0lf May 29 '12 at 8:34
In this case I think it should be bytes, since that's what the string consists of. (It's purely coincidental if the bytestream is composed entirely of valid UTF-8 sequences.) – breadbox May 29 '12 at 17:53
This looks more readable than the average Perl script! – Agos Jun 1 '12 at 9:56

Python 781 731 605 579 Chars

There are a lot more and much better answers from when I first saw this, but I did waste a lot of time on my python script so I am going to post it any way, it would be awesome to see suggestions to further shorten it,

Edit : thanks to Ed H's suggestions 2 chars chopped, to go further I might have to restructure a lot of thing here which is going to take some time

s="e |nd|-We| a|-(Ooh|N| what|ive| go|ay it-|I|er|G|o |make5 |D| th| othH |A| tF|ing |nna |tell|'s been|'rS|-You|-N4| know|L5 up|PR | you|evHK>| how I'm feeling-|O, g7)|O)9gL, n4gL-(G7)|-I just wa>=53Gotta EuRHstaR-.|Q've8n eachBfor sFlong:r heart<Pch?but:;toFshy@sJInsidSwSboth8M<K?onQ8CSgame6we;go>plJ|9g79let5 down9runProuR6desHt59Ecry9sayKodbye9=P lie6hurt5-|\n|Q;nFstrangHs@love:8CSrules6sFdFI-A full commitment'sM I'mCink?of: wouldn't getCis fromPnyBguy0/AR if5Psk me3Don't = me5;toFbliR@see-..2211-/0..";i=83
print s

After the first time that I manually produced the string(very tedious), I wrote a function to recursively find the pattern replacing which was most profitable(at that step), which gave me a solution but it turned out to increase the size by 10 chars.

So, I made my algorithm a little less greedy by instead of doing the final ranking only on 'characters reduced', ranking on a function of 'characters reduced', 'length of pattern' and 'counts of pattern'

pattern length = length count = count

rank = [(length-1)*count - length - 2] + lengthWeight * length + countWeight * count

Then I asked my poor laptop to run infinitely, assigning random values to lengthWeight and countWeight and get different final compression sizes, and store the data for minimum compression sizes in a file

In half an hour or so it came up with the above string (I tried to tinker with it further to see if I could shorten the code), and it won't go any lower, I guess I'am missing something here.

here's my code for it, also max_pattern is very slow (Note: code spits out a string similar to form in my previous version of the solution, I manually worked through it to get the current form, by manually I mean, manually in python shell)

import itertools

global pretty
global split
split = False
pretty = False

# try to keep as much visibility as possible
def prefrange():
    return range(32,127) +  ([] if pretty else ([10, 9, 13] + [x for x in range(32) if x not in (10, 9, 13)] + [127]))

def asciichr():
    return [chr(x) for x in prefrange()]

def max_pattern(s, o, lenw, numw):
    l = len(s)
    patts = []
    for c in range(l/2+1,1,-1):
        allsub = [s[i:i+c] for i in range(0, l, c)]
        subcounts = [[a, s.count(a)] for a in allsub if len(a) == c]
        repeats = [(x, y, ((c-o)*y - o*2 - c)) for x, y in subcounts if y > 1]
        ranks = [(x, y, (z + lenw*c + numw*y)) for x,y,z in repeats if z > 0]
        patts = patts + ranks
        return sorted(patts, key=lambda k: -k[2])[0]
        return None

def sep():
    return '~~' if pretty else chr(127) + chr(127)

def newcharacter(s):
    doable = [x for x in asciichr() if x not in s]
    if len(doable) == 0:
        doable = list(set(x+y for x in asciichr() for y in asciichr() if x+y not in s and x+y != sep()))
        if len(doable) == 0:
            return None
    return doable[0]

def joined(s, l):
    one = [x for x in l if len(x)==1]
    two = [x for x in l if len(x)==2]
    return ''.join(reversed(two)) + sep() + ''.join(reversed(one)) + sep() + s

def compress(s, l=[], lenw=0, numw=0):
    newchr = newcharacter(s)
    if newchr == None:
        if not l:
            return s
        return joined(s,l)
        ptn = max_pattern(s, len(newchr), lenw, numw)
        if ptn == None:
            if not l:
                return s
            return joined(s, l)
        s = s.replace(ptn[0], newchr)
        s = ptn[0] + newchr + s
        return compress(s, l, lenw, numw)

def decompress(s):
    lst2, lst, s = s.split(sep(),2)
    li = [lst2[i:i+2] for i in xrange(0, len(lst2), 2)]+list(lst)
    for c in li:
        x, s = s.split(c, 1)
        s = s.replace(c, x)
    return s

def test(times):
    import random
    rnd = random.random
    tested = {(1001, 1001): (10000, 10, False),}
    org = open('text').read()
    minfound = 1000
    for i in xrange(times):
        l,n = 1001,1001
        while (l,n) in tested:
            # i guess this would be random enough    
            xr = lambda: random.choice((rnd(), rnd()+rnd(), rnd()-rnd(), rnd()*random.choice((10,100,1000)), -1*rnd()*random.choice((10,100,1000)),))
            n = xr()
            l = xr()
        sm = compress(org, l=[], lenw=l, numw=n)
            dc = decompress(sm)
            tested[l,n] = (len(sm), len(sm)/float(len(org)), 'err')
        tested[l,n] = (len(sm), len(sm)/float(len(org)), dc==org)

        if len(sm) < minfound:
            minfound = len(sm)
            print '~~~~~~~!!!!!!! New Minimum !!!!!!!~~~~'
    return tested

if __name__ == '__main__':
    import sys
    split = False
        if sys.argv[2] == 'p':
            pretty = True
        pretty = False
    org = open(sys.argv[1]).read()
    sm = compress(org,lenw=l,numw=n)
    print 'COMPRESSED -->'
    print sm, len(sm)
    print len(sm)/float(len(org))
    print 'TRYING TO REVERT -->'
    dc = decompress(sm)
    #print dc
    print dc==org
share|improve this answer
1. An extra variable for \n would cost 5 chars and save 9. 2. Extra space in in (g,l..). 3.join(..) works as well as join([..]) (at least in 2.7). – ugoren Jun 3 '12 at 6:39
thanks, further reduced size by 50 chars... cool – Optimus Jun 3 '12 at 7:34
126 less chars, this is fun – Optimus Jun 3 '12 at 21:24
Your program seems to be only 587 bytes long (unix formatting) -- one less than the several answers that are 588. – r.e.s. Jun 7 '12 at 2:46
thanks for pointing it out, actually my vim adds endline to the end of python file – Optimus Jun 7 '12 at 7:46

PHP 730 707 characters

<?$l=' you';$n='Never gonna';$z="give$l up";$m="
$n $z
$n let$l down
$n run around and desert$l
$n make$l cry
$n say goodbye
$n tell a lie and hurt$l

We've known each other for so long
Your heart's been aching but
You're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it
";$p="(Ooh, $z)";$r="($z)";$g=" how I'm feeling";$s="$n give, $n give";$t="I just wanna tell$l$g
Gotta make$l understand";echo"We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
And if$l ask me$g
Don't tell me$l're too blind to see
share|improve this answer
You could make this shorter by putting "You" and "you" into variables. – Polynomial May 29 '12 at 8:14
@PeterTaylor, that's a typo thanks for brining to notice. – l0n3sh4rk May 29 '12 at 15:33
@Polynomial, done! – l0n3sh4rk May 29 '12 at 16:00
$s="Never gonna give... can be shortened with $n. – Mr. Llama May 29 '12 at 21:08
@GigaWatt, thanks! – l0n3sh4rk May 30 '12 at 1:42

Perl - 589 588 583 579 576 Bytes

Each rule consists of a 1 char head, a body and an underscore. As long as rules can be chopped off the beginnig, the head of the rule is replaced with its body in the rest of the Text. The head of the first rule is given, the heads of all following rules are generated from the variable $i.

Since the head for the next rule is placed on the beginning of the text by the previous rule, the last rule will create a character that wont be removed anymore. I had to chose a range of names where the last one would be "W", so i could remove the original "W" from the start of the lyrics, and had it replaced by the rule substitution.

The encoding was done by a Python script using a simple hillclimbing algorithm.

Here is the Perl code:

$_="qou_on_er_Hh_ w_
(Ooh_ell_ a_ay it
_'reHoC_g0na _makeR _ve_ oth1 _ing_e ___A_t's been _o _D_4, gP)_
Yq_G_ t_I_ know_
NT_nd _ how I'm feel=
_N_O_i;R up_4)Kgi;, nTgi;
(GP)_ yq_
I just3annaH5RMGotta :und1stand
V_e;r 9_
We';Jn each<for sCl0gFr hearBach= butF8shyHCs7Insid>w>bothJ3haBgo= 0
WeJ2>gam>aLwe'r>9pl7_KgPKletR downKrun6rqLaLdes1tRK:cryKsay goodbyeKt56 li>aLhurtR
A full commitment's3hat I'm2ink= ofF3qldn't get2is from6ny<guySUALifR6sk meMD0'tH5 meR8bliLtCsee
USVV";$i=48;eval"s/$1/$2/g"while s/(.)(.*?)_/chr($i++)/se;print

(I find it remarkable that the compressed Text contains "hearBach" :D)

And here the Python code that generates it:

import collections, sys
text ='\r\n','\n')
text = text[1:]
names = list(["q"] + map(chr, xrange(ord('0'), ord('W'))))
done = False
name = ""
while not done:
    done = True
    best = (0, None)
    for m in xrange(1, len(text) / 2 + 1):
        counter = collections.Counter()
        for i in xrange(0, len(text) - m + 1):
            if not '_' in snippet:
                counter[snippet] += 1
        for snippet in counter:
            n = counter[snippet]
            gain = n * m - n - (m + 1)
            if gain > best[0]:
                actual_gain = len(text) - len(text.replace(snippet,"")) - n - (m + 1)
                if actual_gain > best[0]:
                    best=(actual_gain, snippet)
    if not done:
        snippet = best[1]
            lastname = name
            name = names.pop()
            while name in 'ADGION':
                text = name + '_' + text
                name = names.pop()
            while name in '?@':
                text = '_' + text
                name = names.pop()
            sys.stderr.write('Warning: out of names.\n')
            lastname = "0"
        text = snippet + '_' + text.replace(snippet, name)
sys.stdout.write(name + text)
sys.stdout.write('";$i=' + str(ord(lastname)) + ';eval"s/$1/$2/g"while s/(.)(.*?)_/chr($i++)/se;print')
share|improve this answer
This is impressive! But you really should do some golfing of your Perl. In that short snippet you've got at least five chars that can be reduced (unneeded parens and the like). – breadbox Jun 6 '12 at 22:14
5 chars? I got one for using a for-loop, 2 for the eval-parens and 1 for the unused regex-modifier, but now i'm stuck. (still ahead of Optimus again :D) – quasimodo Jun 7 '12 at 14:21
Try using the suffix form of while for the loop; this lets you forgo the braces as well as the parentheses. Another idea: figure out how to use say instead of print to do the output. – breadbox Jun 7 '12 at 16:11
@quasimodo not anymore you're not. Just wanted to say that once :P – Optimus Jun 8 '12 at 4:49
Its a lot of fun, yes. But i think i really should stop here, i dont want to think of all the hours i wasted on this :D – quasimodo Jun 9 '12 at 9:51


720 bytes / characters:

(Reproduced here with extra whitespace so you can see the formatting)

(let [r{\&" and "\Y"You"\0"\n"\1" you"\2" gonna"\3"give"\5" up"\6"ever"\F" how I'm feeling"\T" to"}
      s str 
      n"0N62 "
      c(s n"315"n"let1 down"n"run around&desert1"n"make1 cry"n"say goodbye"n"tell a lie&hurt10")
      p"0(Ooh, 315)"
      q(s n "3, n62 3")
      w(s "0We've known each other for so long0Yr heart's been aching but0Y'reTo shyT say it0Inside we both know what's been going on0We know the game&we're2 play it0")
      u(s "I just wanna tell1F0Gotta make1 understand0")
      v"We're no strangersT love0Y know the rules&so do I0A full commitment's what I'm thinking of0Y wouldn't get this from any other guy0"
      R(s v u c w"And if1 ask meF0Don't tell me1'reTo blindT see0"c c p p o q g o q g\0 w\0 u c c c)]
  (apply s(map #(r% %)R)))      
share|improve this answer
What's the minimal bytes version of this? – CalculatorFeline May 17 at 15:54

Python 2.7, 975 803 bytes

Not the greatest - I (now) wish python did formatting expansions like so. Alas it doesn't.

Edit: Simulated expansion with alternate formatting syntax (sort of..)

print("""We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy

And if you ask me{8}
Don't tell me you're too blind to see

{0}, {2})
{0}, {2})



%s"""%tuple(['{1}{2}\n{1}{3}\n{1}{4}\n{1}{5}\n{1}{6}\n{1}{7}']*6)).format(*"(Ooh|Never gonna |give you up|let you down|run around and desert you|make you cry|say goodbye|tell a lie and hurt you\n| how I'm feeling|(Give you up)|I just wanna tell you|Gotta make you understand|give, never gonna give|We've known each other for so long\nYour heart's been aching but\nYou're too shy to say it\nInside we both know what's been going on\nWe know the game and we're gonna play it".split('|'))
share|improve this answer

589, C (only library function is putchar)

c,k;main(n){char*s="&-P;nDstrKgQsLlove>@rules<sDdD-i7Rfull commitUnMVtASTkEof> wWldNget Sis from Ky?guy10-XifYask U6doNF U5OblTdLseeG//G442201//&383letYdown3run arWnd<desQt53Bcry3sZ[odbye3F Rlie<hurt5G&7P've:n each?for sDlong>r hear=achEbut>OshyLsH7Tside P boS: V=[Eon7P@gaU<P;CplHG&7i just wKnRF56[ttRBundQstKdG/&J)3I, 9IG(-8)G&79&J, 8)G& yW& howAfeelTg7&G-&IYup&nevQ C& know&'re & X&Mbeen &7yW& oSQ &: Se & -i'm &makeY&[nnR&o &Tg &tell&\n&Zit&give&(-ooh&an& tD&t's &n't &;toD&we&er&a &th&in&me&wha&ou&Kd &5 &ay &go";for(k|=32*!n;c=*s++;c-38?n?0:c-45>48U?k=!putchar(c-k):main(c-45):n--);}
  • Table of substitution rules where characters in the range -.._(45..90) specify which rule to apply, thus some 48 rules (45, c-45>U48 in code), other characters are to be printed

  • rules are delimited by the '&' character (38 in code, n is decremented until the zero and thus s points to the correct rule)

  • rule 0 indicates that the next character should be capitalized (by setting k=32 in code), this frees up more space to add a larger continuous range of characters for rules

  • main(..) is called with 1 (as per zero argument C program convention), and thus rule 1 is the root rule

Evolution of code

  • shaved a further 9 bytes off thanks to ugoren's suggestion

  • shaved another 36 bytes off by creating table algorithmically rather than by hand, and via the "'" tip

  • shaved another 15 bytes off by changing the table from a char*[] into a single string where '&' delimits portions

  • shaved yet another 19 bytes thanks to more tips from ugoren

  • shaved 31 bytes by adding more rules, made special rule to capitalize, thus allowing more space for rule indexes.

  • shaved 10 bytes off thanks to yet more tips from urgoren, and tweaking rules slightly

share|improve this answer
*p>>4^3?putchar(*p):e(r[*p-48]) – ugoren Jun 4 '12 at 18:58
ah! thank you, now included in the code. – baby-rabbit Jun 4 '12 at 22:29
Also, the "\'" translation isn't needed. "We're" is a valid string. ing is a better candidate. – ugoren Jun 5 '12 at 6:30
Nice change. Several standard golfing tricks can be applied: d(int n)->d(n). Change *s=='~' to *s-'~' and reverse the ?:, also saving parenthesis around !n?..:0. Using 126 instead of '~'` is useless, but why ~? – ugoren Jun 6 '12 at 6:31
Also, you don't need two functions - make main recursive. The initial call is main(1) instead of d(0), but it can be dealt with (maybe a leading ~ in s). Also, the best alternative to ~ is a tab (ascii 9 - single digit). – ugoren Jun 6 '12 at 6:42

Perl, 584 578 577 576 575 571 564 554 553 540

This solution follows the same basic approach as most of the others: given an initial string, perform repeated substitutions of repeated portions of the text.

The substitution rules are specified by a single character, preferably one not occurring in the output text, so a rule of length L and occurring N times will save approximately N*L-N-L-1 (N*L is the original length of all occurrences, but the substitution character occurs N times, and the literal text itself has length L, and the rules are split by a separating character.) If the substitution characters are explicitly specified, then the savings is reduced to N*L-N-L-2. Given that most languages can compute a character with chr() or similarly short code, the first approach tends to be superior.

There are some drawbacks to computing the substitution character, the most significant being the need for a continuous range of ASCII characters. The output uses mostly lowercase letters, but there are enough uppercase letters and punctuation to require either replacing a character with itself, remapping a few characters in a fixup stage afterwards, or ordering the rules such that replacements with problematic characters occur earlier. Using a language that replaces using regexes also means that there are gotchas for characters that have special meaning within a regex: . + * \ ?

My original approach had 63 bytes in the decoder and 521 in the rules. I spent a lot of time optimizing the rules, which can be tricky particularly with the short rules as they can overlap. I got the decoding down to 55 bytes and the rules down to 485 by cheating the formula a little bit. Normally, a 2-character rule that occurs 3 times or a 3-character rule that occurs twice would not actually save any length, but there is a loophole - which also allows for making up words that aren't part of the output ;-).

I do make use of control characters in this solution, so the solution is provided here base64-encoded.


And here it is as a slightly more readable (but less executable) version.

tra^O^Ps^R^Glo^N^Y^\^U^Drules^M^Ts^Gd^GI^AA^Sull commit^Hnt^Ws^Qhat,^Uink^[of^Y^Q^Kldn^Wt^Eet^Uis^Srom^Mny^_^Euy.'A^Tif^X^Msk ^H-Don^Wt ^^ ^H^X^Zto^Gbli^Tt
Weeee  goto meow^Gsound avenger w t f^L ^Rhi^N'"=~/.?\D/g,split
0,' y^K0^AY^K0^Wr^D0i^O 0 kn^I0nna 0tell0 ^Fh^P0 0^V^X up0e^Nr^Eo^]0t^Ws b^Cn 0ay it^A0^AN"0^A(Ooh0^A^A^B^W^N^\n each^_^Sor s^Glo^O^Yr hear#ach^[but^Y^Zto
$Inside^Q^Db^Fh^\^Qha#go^[on^A^B^\^U^Dga^H^M^Twe^Zgo^]pl$0&)%g^V, n"g^V^A(G!)0&,^E!)0make^X 0^A%g!%let^X d^In%run^Mr^K^Ta^Tdes^Pt^X%*cry%say^Eoodbye%^^^M lie^M^Thurt^X0 I^Wm0 h^I,^S^Cli^O^A0^AI just^Qa^]^^^X-G^Fta *u^L^Psta^L+')[$=]:egwhile$=;print

However, I suspect this still isn't the minimum, as Ed H. points out that the php decoding is the shortest at 44 bytes and I have seen room for improvement in the rules he is using. I do have a 52-byte decoder in Perl but I was unable to use it for this solution as I needed to run through the range in reverse.

share|improve this answer

PHP, 591 585 568 564 bytes

82---")),'x z',' (I');
share|improve this answer
I think you may be missing something in the code; it seems to cause an error: – w0lf Jun 2 '12 at 21:16
This is PHP 5.4. For older versions replace [z=>xx] with array(z=>xx) (and get 590 bytes) – a sad dude Jun 2 '12 at 21:42
yes, works. Nice, +1! – w0lf Jun 3 '12 at 7:09
A shorter PHP solution (using rules from my ruby code): – Ed H. Jun 9 '12 at 17:46
Yeah, guess "LOWERCASE EVERYTHING!" was a bad idea after all ) nice! – a sad dude Jun 11 '12 at 7:42

JavaScript, 854 chars (added newlines for "readability")

var a="We're no strangers to love:You know the rules and so do I:A full commitment's what I'm thinking of:You wouldn't get this from any other guy:I just wanna tell you how I'm feeling:Gotta make you understand:Never gonna give you up:Never gonna let you down:Never gonna run around and desert you:Never gonna make you cry:Never gonna say goodbye:Never gonna tell a lie and hurt you:We've known each other for so long:Your heart's been aching but:You're too shy to say it:Inside we both know what's been going on:We know the game and we're gonna play it:And if you ask me how I'm feeling:Don't tell me you're too blind to see:6:7:8:9:10:11:6:7:8:9:10:11:(Ooh, give you up):31:(Ooh):Never gonna give, never gonna give:(Give you up):33:34:35:12:13:14:15:16:4:5:6:7:8:9:10:11:6:7:8:9:10:11:6:7:8:9:10:11".split(':'),
share|improve this answer
+1 Very nice approach. – Tomalak Jun 1 '12 at 14:17

JavaScript 789 characters

My javascript (prints with "document.write()"):

eval('f="18927993248999".replace(/1/g,"Were no strangers to love4You6тe rules and so do I4A full commitmentжs what Iжm тinking of4You wouldnжt get тis from any oтer guy4ю8/g,"I just wanna tellйhow Iжm feeling4Gotta makeйunderstand44ю9/g,"Neverгgiveйupвгletйdownвгrun around and desert youвгmakeйcryвгsay goodbyeвгtell a lie and hurt you44ю2/g,"Weжve known each oтer for so long4Your heartжs been aching but4Youжre too shy to say it4Inside we boт6whatжs been going on4We6тe game and weжreгplay it4ю7/g,"And ifйask me how Iжm feeling4Donжt tell me youжre too blind to see44ю3/g,"ц, gяц, gяц)вгgive, neverгgive4(Gяц)вгgive, neverгgive4(Gя4 ют/g,"thюя/g,"iveйup)4юй/g," you юв/g,"4Neverю4/g,"</br>юц/g,"(Oohю6/g," know юг/g," gonna юж/g,"\'");document.write(f);'.replace(/ю/g,"\").replace(/"))

I changes some common words and phrases with cyrilic letters and then changed them back with the replace() function.

After I shorten the lyrics, I shortened my program with the same method, and execute the code with eval().

share|improve this answer

Ruby, 741 678 657 627 619 bytes

28.times{|i|_.gsub!'120=34589%#^*&@!/?><[]|{}:;~'[i],"We? n{strangers] love/>& the rules!s{d{I/A full commitment's}hat:thinking of/>}ouldn't~et this from;ny<guy/+I just}anna [*@/Gotta make* understand/0+=g#=let* down=run;round!desert*=make* cry=say~oodbye=[; lie!hurt*/+/N^+We've&n each<for s{long/>r heart's been;ching but/>?]{shy] say it/Inside}e both&}hat's been~oing on/We& the~ame!we?|play it/+And if*;sk me@/Don't [ me*?]{blind] see/+8899+(Ooh,~#)/+(Ooh)/N%, n%/(G#)/+^give+ive* up+ever|+ you+ know+ how:feeling+;nd +\n+'re+You+ other +tell+ to+~onna +o + w+ I'm + a+ g".split('+')[i]}
puts _

This is an iterative symbol expansion. For each of the 28 characters of the string in the first argument to gsub!, all occurrences of that character in _ are replaced by the appropriate section of the second string (separated by + characters).

share|improve this answer

Python, 573 chars

My sed solution won't go any further, and it was beaten by several people, so I went for a new approach.
Unfortunately, it's only good enough for 2nd 3rd place (as of now) - Ed H. is still much ahead of me.

x="WM n=straQRsF=loB7Erules3s=d=IXA full commitSnt'sKhatVFhink;of7KTldn'tUetFhis fromLny9guy.-AC if?Lsk S1Don'tFP S?<bliCF=see//X82)8002)-.//"
for r in"XXW'BHn each9for s=loQ7r hear6ach;but7<shyF=s@InsideKe bothHKha6go;onXWEgaS3weM:pl@|XI justKannaFP?1Gotta >uCRstaC/|X4g24let? down4runLrTC3desRt?4>cry4sayUoodbye4tPL lie3hurt?|2)J)4giB, n5giBX(G| howV feeliQX|iB? up|LC |XN5|eBr:|t's been |XYT|J,U| othR |Uonna |iQ |MFo=|o |make? | yT|ay itX|A|ve|nd|D|HFhe | t|G| know|I|X(Ooh| w| a|'re|N|O|ell|ng|er|me|ou| g| I'm|We|\n".split("|"):x=x.replace(chr(i),r);i+=1
print x


  1. The main idea was borrowed from Ed H. - using consecutive characters for replacement, instead of specifying them in each replacement rule.

  2. My way to deal with characters which exist in the song is different from Ed's - I simply make sure to translate each to itself (and if it's always followed by something, add it as well, which only worked for W).

  3. The code is generated by a script that looks for good translations. At first, I used a greedy algorithm, which simply takes the one that gives the best reduction. Then I've found that tweaking it to prefer longer strings improves it a little. I guess it's still not optimal.

share|improve this answer
I too tried to generate the string through a greedy algorithm, which ended up adding 11 chars. I've noticed that all python solutions converge to a more or less similar optimal form. I guess it goes with their 'only one obvious way to to it'. – Optimus Jun 5 '12 at 23:44
In the past, I've occasionally found it works to modify such scripts to use a mostly-greedy algorithm instead, adding a little bit of slop to randomly permit a second- or third-place choice to be selected, and then running it repeatedly looking for improvements. (Or if I'm motivated enough, code up a heuristic search like simulated annealing.) – breadbox Jun 6 '12 at 6:59
I ran a simple simulation modifying my greedy algorithm ranking on an expression with variable coefficients for the length of the pattern and number of counts, did around only around 100000 simulations (finding largest pattern is very slow) with random values for coefficients (between -100 and 100 with Gaussian distribution around zero), it found a pair for which gave 15 less chars than my manually crunched string reducing my solution to 590 chars. I guess I'll let it run overnight to see if it yields something better. – Optimus Jun 6 '12 at 17:24
@Optimus, I think the first two translations are obvious, and you can hard-code them. This speeds my search logic significantly. My logic is awfully slow - ~30 seconds for a single run - so it isn't useful for simulation. I can try to optimize it. – ugoren Jun 6 '12 at 20:09
@ugoren I posted my simulation code in my answer it runs in about ~0.5 secs per run and yes I already do hardcode the first 3-4 steps which reduces it by about 30 chars, can't seem to get past a minimum, can you take a look and suggest something. – Optimus Jun 7 '12 at 9:09

Golfscript, 708 702 699 691 bytes

"Never gonna ":g;
"I just wanna tell you how"" I'm feeling":i"
Gotta make you understand"++:j;
{n"give you up
let you down
run around and desert you
make you cry
say goodbye
tell a lie and hurt you"n/{n g@}%}:^;"

We've known each other for so long
Your heart's been aching but
You're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it
"We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
"And if you ask me how"i"
Don't tell me you're too blind to see"
"(Ooh, give you up)
"g"give, never gonna give
(Give you up)"++.n\_
share|improve this answer
I may be missing something, but I don't see a reason for the blocks. Can't you just use e.g. " I'm feeling":i;? – Peter Taylor May 30 '12 at 9:49
@PeterTaylor You're perfectly right; the blocks are needed only when I assign multiple strings to a variable. Thanks a lot! This strips off 6 chars and just gave me another idea I can try out. – w0lf May 30 '12 at 10:04
Yep, it worked! Instead of assigning a block to the variable j, I assigned three concatenated the strings (deleted { and }, but added ++ for concatenation). This allowed me to declare i inline, when composing the contents of j. – w0lf May 30 '12 at 10:08
I think you can save a few more by pulling a leading newline into g and for the chorus using a single string with newlines and then n/g* – Peter Taylor May 30 '12 at 11:37
Great idea! I could not alter g as it is also used towards the end (actually possible, but would've costed 1 more char in the end). However, the split/fold approach to insert g at the beginning of every line is a great character saver. – w0lf May 30 '12 at 12:53

Naive sh/echo - 810 bytes

A="ever gonna"
D=" you"
B="ive$D up"
C="$A give"
echo "We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
I just wanna tell$D how I'm feeling
Gotta make$D understand"
echo "
N$C$D up
N$A let$D down
N$A run around and desert$D
N$A make$D cry
N$A say goodbye
N$A tell a lie and hurt$D"
echo "
We've known each other for so long
Your heart's been aching but
You're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it"
echo "And if$D ask me how I'm feeling
Don't tell me$D're too blind to see"
echo "
(Ooh, g$B)
(Ooh, g$B)
N$C, n$C
N$C, n$C
echo "I just wanna tell$D how I'm feeling
Gotta make$D understand"
share|improve this answer

JavaScript, 1428 1451 883* characters

Definitely not the shortest solution, but here it goes.

d="(Give you up):(Ooh):(Ooh, give you up):A full commitment's what I'm thinking of:And if you ask me how I'm feeling:Don't tell me you're too blind to see:Gotta make you understand:I just wanna tell you how I'm feeling:Inside we both know what's been going on:Never gonna give you up:Never gonna give, never gonna give:Never gonna let you down:Never gonna make you cry:Never gonna run around and desert you:Never gonna say goodbye:Never gonna tell a lie and hurt you:We know the game and we're gonna play it:We're no strangers to love:We've know each other for so long:We've known each other for so long:You know the rules and so do I:You wouldn't get this from any other guy:Your heart's been aching but:You're too shy to say it:".split(/:/);"hk3l76o9bdcefojmn8g45o9bdcefo9bdcefo221a01a0oimn8go76o9bdcefo9bdcefo9bdcef".split("").map(function(i){return d[parseInt(i,25)]}).join("\n")

Solution logic is pretty simple:

"012345".split("").map(function(i){return d[parseInt(i,25)]}).join("\n")

*Of course the solution becomes a lot shorter when taking unique lines instead of unique words.

share|improve this answer

Ruby, 1014 bytes

I am just learning programming, so I'm not going to break any records here. But, this was a fun task.

def c
  wonts = ['give you up', 'let you down', 'run around and desert you', 'make you cry', 'say goodbye', 'tell a lie and hurt you']
  wonts.each do |w|
    puts "Never gonna #{w}"

def b
  puts "\n"

def never
  puts "Never gonna give, never gonna give
(Give you up)"

def v1
puts "We're no strangers to love
You know the rules and so do I
A full commitment's what I'm thinking of
You wouldn't get this from any other guy
I just wanna tell you how I'm feeling
Gotta make you understand"

def v2
  puts "We've known each other for so long
Your heart's been aching but
You're too shy to say it
Inside we both know what's been going on
We know the game and we're gonna play it"

def s1
puts "And if you ask me how I'm feeling
Don't tell me you're too blind to see"

def s2
puts "I just wanna tell you how I'm feeling
Gotta make you understand"

def soul
2.times {puts "(Ooh, give you up)"}
puts "(Ooh)"
puts "(Ooh)"

share|improve this answer

Java - 903 Bytes

Here is my Java take at it, no compression algorithms used, just a dictionary.

In a single line:

class R {static String n(String w){return "Never gonna "+w+"\n";}public static void main(String[] args){String d[]={"(Ooh, give you up)\n","(Ooh)\n",n("give you up")+n("let you down")+n("run around and desert you")+n("make you cry")+n("say goodbye")+n("tell a lie and hurt you")+"\n","(Give you up)\n",n("give, never gonna give"),"We've known each other for so long\nYour heart's been aching but\nYou're too shy to say it\nInside we both know what's been going on\nWe know the game and we're gonna play it\n","\nI just wanna tell you how I'm feeling\nGotta make you understand\n\n","We're no strangers to love\nYou know the rules and so do I\nA full commitment's what I'm thinking of\nYou wouldn't get this from any other guy","And if you ask me how I'm feeling\nDon't tell me you're too blind to see\n\n","\n"};int n[]={7,6,2,5,8,2,2,0,0,1,4,3,1,4,3,9,5,6,2,2,2};for(int i:n){System.out.print(d[i]);}}}

And pretty printed for readability

class R {
    static String n(String w) {
        return "Never gonna " + w + "\n";

    public static void main(String[] args) {
        String d[] = {"(Ooh, give you up)\n",
                n("give you up") +
                        n("let you down") +
                        n("run around and desert you") +
                        n("make you cry") +
                        n("say goodbye") +
                        n("tell a lie and hurt you") +
                "(Give you up)\n",
                n("give, never gonna give"),
                "We've known each other for so long\nYour heart's been aching but\nYou're too shy to say it\nInside we both know what's been going on\nWe know the game and we're gonna play it\n",
                "\nI just wanna tell you how I'm feeling\nGotta make you understand\n\n",
                "We're no strangers to love\nYou know the rules and so do I\nA full commitment's what I'm thinking of\nYou wouldn't get this from any other guy",
                "And if you ask me how I'm feeling\nDon't tell me you're too blind to see\n\n",
        int n[] = {7, 6, 2, 5, 8, 2, 2, 0, 0, 1, 4, 3, 1, 4, 3, 9, 5, 6, 2, 2, 2};
        for (int i : n) {
share|improve this answer

Ruby (549) binary chars

I recently found that you can enter arbitrary binary characters (including those with value between 128 and 255) in ruby strings, as long as you follow some simple ruby escaping rules. Packing information into binary enables more efficient compression. Unfortunately, display of the "code", when written in binary, is problematic; thus, I created a generic program that reads in an existing ruby text program (assumed to not use chars with value > 127), and creates a secondary ruby source code that does exactly the same thing. This secondary code uses one long string with binary characters in it, and can be shorter than the original, in terms of number of bytes.

Here is the source "code-packer.rb"; it transforms input ruby source code into equivalent "compressed" output ruby source code. All it really does is binary packing of the original source code string, along with printing code for binary unpacking of the binary string.

Addendum: It appears that the original code-packer produces code that does not represent valid UTF-8 encoded binary strings, and thus is technically invalid according to the rules. To make it valid, we must use only character values up to 128. I recently read the UTF-8 encoding rules, and trying to use all 8 bits and generate UTF-encoded strings is not fruitful because most binary strings (over half of all possibly binary strings of any given length) will not represent valid UTF-8 encodings. So using straight up 7-bit ascii code is more efficient. I've modified the code-packer code below to produce only unreadable 7-bit ascii strings by replacing "256" with "128" and "<<8" with "<<7". This makes the packing compression idea not worthwhile for small programs (< 700 or so bytes), although it does still reduce Actven's 1000+ byte solution down to 937 bytes.

#!/usr/bin/env ruby
# This program takes a ruby file as input
# and outputs an equivalent ruby file as output that
# is functionally equivalent, but "compressed". 
# Compression is done by creating a string representing the original program
# which contains unprintable characters (including chars with value between 128 and 255)
# The string is just a binary packed version of the input program text.
# the "decompression" algorithm for the string is written in ruby as part of the output, 
# along with an eval on the decompressed string. 
# NOTE: assumes input file doesn't contain any unprintable chars with value > 126, otherwise this will fail.

DEBUG=false # turn on debug to produce larger code that uses code sequences for the unprintable characters, 
         # and prints debugging information, and ruby code instead of executing it.

# returns array of frequency counts for str
def frequency_count(str)
  str.bytes {|b| freq[b]+=1}

# figure out an appropriate contiguous
# range of characters (via ASCII) to use for representing str. 
# returns 
#  1) forward hash of chars, when applied to str, give only chars in the range
#  2) backward hash of chars for undoing #1
#  3) start character of range
#  4) length of range
def translate_range(str)
  freq = frequency_count(str)
  forward_hash = {}
  backward_hash = {}
  left_index = 0
  right_index = 126

  # ignore unused top range
  while right_index > 0 && freq[right_index] == 0
    right_index -= 1

  if right_index == -1
    raise "Error: empty input string"
  last_char_index = right_index

  # figure out chars to swap to get contiguous range
  # we want to minimize number of swaps, so this
  # naive approach won't always yield the best results.
  while left_index < right_index do
    if freq[left_index] == 0
       left_index += 1

    if freq[right_index] > 0
       right_index -= 1

    # found char on left that can move to empty spot on right
    forward_hash[left_index] = right_index
    backward_hash[right_index] = left_index
    freq[right_index] = 1 # mark it as filled.
    left_index +=1
    right_index -=1

  while freq[left_index]== 0
     left_index +=1

  [forward_hash, backward_hash, left_index, last_char_index - left_index + 1]

# translates text into big num of given base, assuming all chars in string
# are in range start_char..(start_char+base-1)
def text_to_num (str, start_char, base)
   num = 0
   if (str[0] == start_char)
     raise "Sorry.. I can't handle strings that start with #{start_char.chr}"
   str.reverse.bytes {|x| num = num * base + (x - start_char)}

# translates back from num to text. 
def num_to_text (num, start_char, base)
   text = ""
   while num > 0 do
      text += (num % base + start_char).chr
      num /= base

# translates from packed num string to original string, without translation part
# will be output into code.. present here for testing.
def packed_num_decode(str, start_char, base)
    while m>0 ; t+=(m%base+start_char).chr;m/=base end

# takes a string used for input into translate, and escapes
# chars with special meaning (^ at beginning, '-' between two chars, backslash)
def tr_escapes(str)
   str.sub("\\","\\\\\\\\").sub(/^\^/,"\\^").sub(/(.)-(.)/m, "\\1\\-\\2")

# takes a binary string, and escapes out characters problematic
# when writing the string within single quotes. 
# this includes ' and \.
def packed_string_escapes(str)
  chars_to_escape = "\\\'"
  escaped_str = ""
  str.bytes {|b| if chars_to_escape.include?(b.chr) 
       escaped_str += "\\" + b.chr
       escaped_str += b.chr

#======== Main program ========
if (ARGV.length != 2)
   puts "Usage: #{__FILE__} <input program> <output program>"
inputfile = ARGV[0] 
outputfile = ARGV[1]

inputtext =
input_info = translate_range(inputtext)

from = ""
to = ""
input_info[0].each_pair {|x,y| from += x.chr ; to += y.chr }
input_mod =, tr_escapes(to))

input_num = text_to_num(input_mod, input_info[2], input_info[3])

packed_num = num_to_text(input_num,0,128).reverse # do reverse so decompression code doesn't have to do it.

if (DEBUG) then 
   packed_num_with_quotes = packed_num.inspect
   operation = "puts \"t=\#\{t.inspect\}\";puts"
   packed_num_with_quotes = "'" +  packed_string_escapes(packed_num) + "'"
   operation = "eval"

start_char = input_info[2]
base = input_info[3]
from = ""
to = ""
input_info[1].each_pair {|x,y| from += x.chr ; to += y.chr }

from_str = tr_escapes(from).inspect
to_str = tr_escapes(to).inspect, "w+") {|f|f.puts("#{pretext}m=0;#{packed_num_with_quotes}.bytes{|x|m<<=7;m+=x};" +
              "t=\"\";while m>0;t+=(m%#{base}+#{start_char}).chr;m/=#{base} end;" +

Although the above can be applied to any ruby code (and could be modified to work with other languages), I've applied it to the following short 556 char solution:

puts"W> n:trangMsL;loT96Ke rules8s;d;I
AJull commit7nt'sHhatVKink= of9HXldn'tRetKisJrom?nyERuy02A@ ifC?sk 75DS'tLU 7C>Lo;bli@L:ee11
I justHannaLUC5Gotta Bu@Msta@1|
Ng4NletC downNrun?rX@8desMtCNBcryNsayRoodbyeNtU? lie8hurtC|

W'T6n eachEJor s;lSg9r hear<?ch= but9>Lo:hyL:FInsideHe both6Hha<Ro= S
W6KeRa78we>QplF|4)O)NgiT, nPgiT
(G|iTC up| howVJeel=
| know|me|?@ |
YX|;s|o |t's been|ing|'re| a|nd|A|makeC | yX|D| othM|ay it
|G| w|I| f|Lh| t|er|
(Ooh|eTrQ|RSna | g|on|ve|ell| I'm|We|ou".split("|").inject{|m,o|m.gsub((i+=1).chr,o)}

The short 556 character solution above is essentially my previous solution, with substitution characters changed, so that the binary packing works better.

If you run the original code-packer program on the above short solution, it will generate a solution that is valid ruby source code that is 549 bytes long. Running this generated ruby source code will print out the required lyrics. Note that although the compressed string is over 100 bytes shorter than the original program, most of the savings is taken up by the decompression code.

If you run the current code-packer program on the above short solution, you get an expansion to 627 bytes. The packed string itself is about 50 bytes shorter, but adding the decompression code blows up the overall size.

Thanks to the commenters for pointing out the technicalities of UTF-8 encoding. To me, the "valid UTF-8" restriction seems rather arbitrary-- if you're going to allow unprintable ascii characters in valid code, why shouldn't you also allow "invalidly coded UTF-8 characters?" The original code-packer still does generate valid ruby source code that really takes up only 549 bytes of storage when run on the 556-byte solution presented above.

share|improve this answer
Is the packed code valid UTF8? – a sad dude Jun 6 '12 at 7:33
Check the problem description. Your byte values between 128 and 255 have to be measured as UTF-8-encoded -- in other words as taking up two bytes each. (This rule change is why I had to withdraw my solution.) – breadbox Jun 6 '12 at 12:22

JavaScript 666 Bytes

Inspired by tkazec's solution.

Check out the blog post I wrote about it, it contains all the sources and explains how I built that code.

You can copy and paste the code into your browser's console. Or try it at

t="We're no strangers to love|YouSrules;so do I|A full commitment's what?think7f|You wouldn't get this from anyLguy^zQnqAnd if:ask me[Don'tEme yRblind=ee{HUH_]|Qq^{x<br>{zxz||xxZKVlet:downVrun around;deseBVMcryVsay goodbyeV8a liFhuB||q eachLfor so long|Your hearPaching but|YRshy=@Inside we bothCwhaPgo7n|WeSgamFwe'reJpl@_U]^|I just wannaEyou[Gotta Munderstand](Ooh)|Z, nX|(GU[ how?feeling|ZNXXTgiveV|NTUiveK)|TeverJSCthe Rou're too QWe've9Pt's been Mmake:L other K:upJ gonna H(Ooh, gFe;E 8C9 Brt you@ay it|? I'm = to s; and : you 9 know8tell 7ing o";c="|{zxq_^][ZXVUTSRQPMLKJHFECB@?=;:987".split("");while(l=c.pop()){t=t.split(l);t=t.join(t.pop())}document.write(t)
share|improve this answer

Malbolge, 12735 bytes


Try it online.

Generated using the tools here.

share|improve this answer

Python 2.7, 845 bytes

Encodes words and then lines as ASCII characters.

print "\n".join([[" ".join(["".join(w[ord(p)-35]) for p in k]) for k in o.split("!")][ord(m)-35] for m in l])
share|improve this answer
I think this is incomplete; it seems to miss the first two paragraphs – w0lf Jun 1 '12 at 20:42
@w0lf. Thanks fixed. – Matthew Snape Jun 1 '12 at 21:24

Your Answer


By posting your answer, you agree to the privacy policy and terms of service.

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