I thought this would be a fun challenge for everyone and I'm curious to see the solutions people come up with.

Print the "12 Days Of Christmas" lyrics

On the first day of Christmas,
my true love gave to me,
A partridge in a pear tree.
On the second day of Christmas,
my true love gave to me,
Two turtle doves,
And a partridge in a pear tree.
...
On the twelfth day of Christmas,
My true love gave to me,
Twelve drummers drumming,
Eleven pipers piping,
Ten lords-a-leaping,
Nine ladies dancing,
Eight maids-a-milking,
Seven swans-a-swimming,
Six geese-a-laying,
Five golden rings,
Four calling birds,
Three french hens,
Two turtle doves,
And a partridge in a pear tree.

Rules

You don't have to worry about capitalization; the entire text can be case insensitive

You can sensibly ignore any punctuation: hyphens can be spaces, and commas and periods can be ignored

There should be a blank line between each verse

You must ordinalize your numbers: "first day of Christmas", "Four calling birds", etc

23 Answers
23

Brainfuck - 2,974

I am rather proud of this one. That sounds like a pretty big number, but keep in mind, I did not use any external compression libraries, and none of the original text is in my program anywhere. None of the other submissions can say that. This is all hand coded. More naive text generators give over 39k for this text, so I would say this is a significant improvement.

Unfortunately, this is about 600 characters longer than its own output, but whatever.
It keeps the characters c,h,m,r,w in an array, and uses that to print all text. Two arrays to the right of twelve spaces each keep track of which day we are on for the count, and for which items we can output.
I may be able to optimize it a bit by reorganizing the memory map to bring the printing characters in between the two counting arrays to avoid such long chains of <<<<<<< and >>>>>>, but that would be a lot of work at this point. I could also probably choose some better seed characters with frequency analysis to minimize incrementing / decrementing, but whatever.

Sure thing. Added ungolfed version. For the day numbers only one bit is set out of twelve, and after outputting it sets the next days bit. For items, the next to the last line sets n bits in a row high and all active get output.
–
captncraigDec 17 '11 at 20:35

Writing this code was a weird kind of golfing exercise: it turns out the maximizing repetition and minimizing the number of distinct characters used are much more important than minimizing raw character count when the relevant metric is size after compression.

To squeeze out the last few chars, I wrote a simple program to try small variations of this code to find the one that compresses best. For compression, I used Ken Silverman's KZIP utility, which usually yield better compression rations (at the cost of speed) than standard Zlib even at the maximum compression settings. Of course, since KZIP only creates ZIP archives, I then had to extract the raw DEFLATE stream from the archive and wrap it in a RFC 1950 header and checksum. Here's the code I used for that:

Highlights of this version the pair of regexps s/e?t? .*/th/,s/vt/ft/, which construct the ordinals for 4 to 12 from the cardinals at the beginning of the gift lines.

This code can, of course, also be compressed using the Zlib trick described above, but it turns out that simply compressing the output is more efficient, yielding the following 338-byte program (in Base64 format, again):

The builtin facilities to format ordinals are helpful, but most of the compression comes from being able to use the same argument list over and over, skipping over fewer and fewer arguments at each run.

As proven by coredump in the comments, the builtin facilities can still be put to good use for the cardinals.

I first hoped that the remaining number of arguments could be used, but I read the spec and I can't do it. The shortes I have is 333 chars: (dotimes(n 12)(format t"on-the-~:R-day-of-christmas my-true-love-gave-to-me ~v*~@{~R-~A ~#[AND-~]~}A-PARTRIDGE-IN-A-PEAR-TREE "(1+ n)(- 22 n n)12'drummers-drumming 11'pipers-piping 10'lords-a-leaping 9'ladies-dancing 8'maids-a-milking 7'swans-a-swimming 6'geese-a-laying 5'golden-rings 4'calling-birds 3'french-hens 2'turtle-doves))
–
coredumpOct 29 '14 at 7:38

Now we're talking. My dream would have been to share the skip counter with the ordinal, but I haven't found a short way to do that.
–
J BOct 29 '14 at 10:27

I like your trick with * to represent "ing". You should be able to get it down to about 440 by: use 12.times instead of (0..11).each; do a single puts with two arguments instead of two puts with one argument; use %w() notation for the days-of-Christmas array. Finally, you can get rid of the reverse by reversing the list, adding an extra ^ to the end of the string, and then using [-i..-1] instead of [0..i].
–
Wayne ConradJan 5 '14 at 18:41

Harnesses Lingua::EN::Numbers, though I'm not 100% convinced it's a good idea when I see the module and its identifier names' lengths. Needs Perl 5.10 or later, run from the command line with a -E switch.

Edit: minor improvements: stop using an array, better use of $_, unneeded whitespace.

+1, Cool! Somebody might complain about using a non-standard module, but if we're allowing any language to be used (including special purpose ones like GolfScript), I don't see why "Perl + Lingua::EN::Numbers" wouldn't be a valid language for a solution. Writing an "Acme::12Days" module and submitting it to CPAN is probably cheating, though. :)
–
Ilmari KaronenDec 8 '11 at 14:57

@Ilmari Karonen when people complain, I usually rename the language to "CPAN". Doesn't happen often.
–
J BDec 8 '11 at 16:15

On the first day of Christmas my true love gave to me A partridge in a pear tree
...
On the twelfth day of Christmas my true love gave to me Twelve drummers drumming Eleven pipers piping Ten lords-a-leaping Nine ladies dancing Eight maids-a-milking Seven swans-a-swimming Six geese-a-laying Five golden rings Four calling birds Three french hens Two turtle doves and A partridge in a pear tree

I had originally included a switching statement to get the "and" on the partridge for all but the first verse. But, because the question absolves us of punctuation, we can just append the "and" to the doves.

This results in line feeds as follows:

On the first day of christmas my true love gave to me a partridge in a pear tree
On the second day of christmas my true love gave to me two turtle doves and a partridge in a pear tree
On the third day of christmas my true love gave to me three french hens two turtle doves and a partridge in a pear tree

+1 Holey crap. You got this working with line feeds as well as the "And" appearing on the last line.
–
Andrew ShepherdDec 8 '11 at 22:08

Well, printing a different text was never an option anyway, and the “And” in the last line uses the same trick like everyone else. Still, I wanted to preserve line breaks which the other solution does not (while being longer, too).
–
JoeyDec 9 '11 at 10:43

This is my first attempt, and I am sure it could be made a lot shorter. The line breaks are for readability. It has three important arrays, one of which holds the name for each day @s, one of which lists off all of the gifts (except for the first one) @a, and one that lists off what gifts have already been given @b. The main mechanism is that each day, it prints @b and then transfers one additional gift from @a to @b.

you can replace rings with r$1s to save 1 more char
–
macekDec 8 '11 at 1:16

@macek I can't do that because perl will interpret the s as being part of the variable name, and the variable $is does not exist. (They are actually i's instead of ones, btw)
–
PhiNotPiDec 8 '11 at 2:47

Congrats on a first solution; works fine. The contest with code golf is 'shortest code possible', so it's standard on this site to do a few things you'd never do in normal code: cut variable and function names to one character, cut whitespace, and a few other things. (It's OK, and expected, to also include the full-length version as you did here to make the approach clearer.) Having done that, you can then ask yourself: "how can I make this shorter?"
–
TynamApr 19 '12 at 10:26

To create the actual code from the hexdump, put it in a file and run xxd -r hexdump > 12days.rb. Then executing ruby1.9.3 12.days.rb will run the code and print the lyrics.
Note that this code requires Ruby 1.9.3 (because it uses Zlib.inflate) , so it won't work with Ruby 1.8.x, 1.9.1 and 1.9.2.

Ilmari's script complains about modifying a read only value without the extra $_=...; characters, but presumably he'd make this 313. You could save several more bytes by tweaking the compression manually like Ilmari did before, maybe achieving 310 or so, but I didn't bother.

Perl, 376 (cheating off another submission) [my original submission]

First, create a perl script called 12days.pl containing :

use IO::Uncompress::Inflate qw(inflate);inflate\*DATA=>'-';
__DATA__

Next, pipe the output from any other submission into 12days.txt and execute the command :

Vola 12days.pl is around 376 bytes and prints the song. ;) Amusingly using rawinflate moves precisely six bytes from the data doc into the code starting from Ilmari's output.

I'd originally set about looking for a Huffman coding module directly, which isn't nearly so dishonest. Yet, sadly CPAN has no modules with English's letter entropy table, which is what you really want when compressing very short strings.

I found that fortune -m Days\ of\ Christmas does not work either, regrettably.

Not the kind of solution I like to read (yet another base-64/gzip, just great), but I really see no reason why you alone would deserve a negative answer score with that. Upvoted to bring some balance; whoever downvoted is requested to inform us of why.
–
J BDec 17 '11 at 18:48

Many have provided a compressed solution so just for the fun of it I decided to put one also. But my original code is posted too, so I don't see what the problem is. Just ignore the gzipped one. I would also like to know why I was being downvoted.
–
VladimirDec 18 '11 at 14:53

If this is a valid solution, then I have an even shorter one written in HQ9+C. (That's HQ9+ with one extra command. You can guess what it does.)
–
Ilmari KaronenDec 8 '11 at 19:50

1

I considered obfuscating the perl one using the eval compress trick to claim I found a regex that compresses really well, but that bloated up around 200 characters. lol
–
Jeff BurdgesDec 8 '11 at 20:00