93
\$\begingroup\$

Write a program that outputs the lyrics to 99 Bottles of Beer, in as few bytes as possible.

Lyrics:

99 bottles of beer on the wall, 99 bottles of beer.
Take one down and pass it around, 98 bottles of beer on the wall.

98 bottles of beer on the wall, 98 bottles of beer.
Take one down and pass it around, 97 bottles of beer on the wall.

97 bottles of beer on the wall, 97 bottles of beer.
Take one down and pass it around, 96 bottles of beer on the wall.

96 bottles of beer on the wall, 96 bottles of beer.
Take one down and pass it around, 95 bottles of beer on the wall.

95 bottles of beer on the wall, 95 bottles of beer.
Take one down and pass it around, 94 bottles of beer on the wall.

....

3 bottles of beer on the wall, 3 bottles of beer.
Take one down and pass it around, 2 bottles of beer on the wall.

2 bottles of beer on the wall, 2 bottles of beer.
Take one down and pass it around, 1 bottle of beer on the wall.

1 bottle of beer on the wall, 1 bottle of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.

Rules:

  • Your program must log to STDOUT or an acceptable alternative, or be returned from a function (with or without a trailing newline).
  • Your program must be a full, runnable program or function.
  • Languages specifically written to submit a 0-byte answer to this challenge are allowed, just not particularly interesting.

    Note that there must be an interpreter so the submission can be tested. It is allowed (and even encouraged) to write this interpreter yourself for a previously unimplemented language.

  • This is different from the output by HQ9+ or 99. Any answers written in these languages will be deleted.

As this is a catalog challenge, this is not about finding the language with the shortest solution for this (there are some where the empty program does the trick) - this is about finding the shortest solution in every language. Therefore, no answer will be marked as accepted.

Catalogue

The Stack Snippet at the bottom of this post generates the catalogue from the answers a) as a list of shortest solution per language and b) as an overall leaderboard.

To make sure that your answer shows up, please start your answer with a headline, using the following Markdown template:

## Language Name, N bytes

where N is the size of your submission. If you improve your score, you can keep old scores in the headline, by striking them through. For instance:

## Ruby, <s>104</s> <s>101</s> 96 bytes

If there you want to include multiple numbers in your header (e.g. because your score is the sum of two files or you want to list interpreter flag penalties separately), make sure that the actual score is the last number in the header:

## Perl, 43 + 2 (-p flag) = 45 bytes

You can also make the language name a link which will then show up in the snippet:

## [><>](http://esolangs.org/wiki/Fish), 121 bytes

<style>body { text-align: left !important} #answer-list { padding: 10px; width: 290px; float: left; } #language-list { padding: 10px; width: 290px; float: left; } table thead { font-weight: bold; } table td { padding: 5px; }</style><script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script> <link rel="stylesheet" type="text/css" href="//cdn.sstatic.net/codegolf/all.css?v=83c949450c8b"> <div id="language-list"> <h2>Shortest Solution by Language</h2> <table class="language-list"> <thead> <tr><td>Language</td><td>User</td><td>Score</td></tr> </thead> <tbody id="languages"> </tbody> </table> </div> <div id="answer-list"> <h2>Leaderboard</h2> <table class="answer-list"> <thead> <tr><td></td><td>Author</td><td>Language</td><td>Size</td></tr> </thead> <tbody id="answers"> </tbody> </table> </div> <table style="display: none"> <tbody id="answer-template"> <tr><td>{{PLACE}}</td><td>{{NAME}}</td><td>{{LANGUAGE}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr> </tbody> </table> <table style="display: none"> <tbody id="language-template"> <tr><td>{{LANGUAGE}}</td><td>{{NAME}}</td><td>{{SIZE}}</td><td><a href="{{LINK}}">Link</a></td></tr> </tbody> </table><script>var QUESTION_ID = 64198; var ANSWER_FILTER = "!t)IWYnsLAZle2tQ3KqrVveCRJfxcRLe"; var COMMENT_FILTER = "!)Q2B_A2kjfAiU78X(md6BoYk"; var OVERRIDE_USER = 36670; var answers = [], answers_hash, answer_ids, answer_page = 1, more_answers = true, comment_page; function answersUrl(index) { return "//api.stackexchange.com/2.2/questions/" + QUESTION_ID + "/answers?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + ANSWER_FILTER; } function commentUrl(index, answers) { return "//api.stackexchange.com/2.2/answers/" + answers.join(';') + "/comments?page=" + index + "&pagesize=100&order=desc&sort=creation&site=codegolf&filter=" + COMMENT_FILTER; } function getAnswers() { jQuery.ajax({ url: answersUrl(answer_page++), method: "get", dataType: "jsonp", crossDomain: true, success: function (data) { answers.push.apply(answers, data.items); answers_hash = []; answer_ids = []; data.items.forEach(function(a) { a.comments = []; var id = +a.share_link.match(/\d+/); answer_ids.push(id); answers_hash[id] = a; }); if (!data.has_more) more_answers = false; comment_page = 1; getComments(); } }); } function getComments() { jQuery.ajax({ url: commentUrl(comment_page++, answer_ids), method: "get", dataType: "jsonp", crossDomain: true, success: function (data) { data.items.forEach(function(c) { if (c.owner.user_id === OVERRIDE_USER) answers_hash[c.post_id].comments.push(c); }); if (data.has_more) getComments(); else if (more_answers) getAnswers(); else process(); } }); } getAnswers(); var SCORE_REG = /<h\d>\s*([^\n,<]*(?:<(?:[^\n>]*>[^\n<]*<\/[^\n>]*>)[^\n,<]*)*),.*?(\d+)(?=[^\n\d<>]*(?:<(?:s>[^\n<>]*<\/s>|[^\n<>]+>)[^\n\d<>]*)*<\/h\d>)/; var OVERRIDE_REG = /^Override\s*header:\s*/i; function getAuthorName(a) { return a.owner.display_name; } function process() { var valid = []; answers.forEach(function(a) { var body = a.body; a.comments.forEach(function(c) { if(OVERRIDE_REG.test(c.body)) body = '<h1>' + c.body.replace(OVERRIDE_REG, '') + '</h1>'; }); var match = body.match(SCORE_REG); if (match) valid.push({ user: getAuthorName(a), size: +match[2], language: match[1], link: a.share_link, }); else console.log(body); }); valid.sort(function (a, b) { var aB = a.size, bB = b.size; return aB - bB }); var languages = {}; var place = 1; var lastSize = null; var lastPlace = 1; valid.forEach(function (a) { if (a.size != lastSize) lastPlace = place; lastSize = a.size; ++place; var answer = jQuery("#answer-template").html(); answer = answer.replace("{{PLACE}}", lastPlace + ".") .replace("{{NAME}}", a.user) .replace("{{LANGUAGE}}", a.language) .replace("{{SIZE}}", a.size) .replace("{{LINK}}", a.link); answer = jQuery(answer); jQuery("#answers").append(answer); var lang = a.language; lang = jQuery('<a>'+lang+'</a>').text(); languages[lang] = languages[lang] || {lang: a.language, lang_raw: lang.toLowerCase(42), user: a.user, size: a.size, link: a.link}; }); var langs = []; for (var lang in languages) if (languages.hasOwnProperty(lang)) langs.push(languages[lang]); langs.sort(function (a, b) { if (a.lang_raw > b.lang_raw) return 1; if (a.lang_raw < b.lang_raw) return -1; return 0; }); for (var i = 0; i < langs.length; ++i) { var language = jQuery("#language-template").html(); var lang = langs[i]; language = language.replace("{{LANGUAGE}}", lang.lang) .replace("{{NAME}}", lang.user) .replace("{{SIZE}}", lang.size) .replace("{{LINK}}", lang.link); language = jQuery(language); jQuery("#languages").append(language); } }</script>

\$\endgroup\$
25
  • 17
    \$\begingroup\$ Whoever closevoted this, the other one is a popularity contest... \$\endgroup\$
    – TheDoctor
    Nov 18, 2015 at 15:19
  • 26
    \$\begingroup\$ This is a subtask of the Create an HQ9+ Interpreter golf. \$\endgroup\$
    – Geobits
    Nov 18, 2015 at 15:25
  • 15
    \$\begingroup\$ Note that quartata had already sandboxed a 99BB catalog several weeks ago and was planning to post it. \$\endgroup\$
    – Alex A.
    Nov 18, 2015 at 17:07
  • 16
    \$\begingroup\$ @GamrCorps Please do not unnecessarily change the rules after the challenge has started. None of the characters in the output should be optional. \$\endgroup\$
    – feersum
    Nov 18, 2015 at 18:42
  • 7
    \$\begingroup\$ To try and head off any newcomers who can't see deleted answers, if you're thinking of using a language with a 99 Bottles song built-in, double check the output matches the desired output of the question. There have already been half a dozen deleted HQ9+ answers, as well as other languages with incorrect outputs. \$\endgroup\$
    – Jo King
    Oct 22, 2018 at 4:05

168 Answers 168

4
\$\begingroup\$

PHP, 250 246 244 233 228 bytes

I believe this is smallest so far. This is based on the 247 shot, with some modifications to further minimise.

Minimised

<?php $b=99;function x($n){return"$n bottle".($n-1?'s':'')." of beer";}$y=" on the wall";while($b){$c=x($b);echo"$c$y, $c.\n",--$b?"Take one down and pass it around":"Go to the store and buy some more",", ".x($b?:99)."$y.\n\n";}

Expanded

<?php

$b=99;
function x($n){return"$n bottle".($n-1?'s':'')." of beer";}
$y=" on the wall";
while(b){
    $c=x($b);
    echo"$c$y, $c.\n",--$b?"Take one down and pass it around":"Go to the store and buy some more",", ".x($b?:99)."$y.\n\n";
}
\$\endgroup\$
7
  • 1
    \$\begingroup\$ As you can run PHP from command line with php -r which is equivalent of perl -e and similar, is acceptable to omit the opening tag. See the related discussion in Running PHP with -r instead of code tags. \$\endgroup\$
    – manatwork
    Nov 19, 2015 at 18:27
  • \$\begingroup\$ Ah, thanks @manatwork, I guess I could then get mine down a bit more. \$\endgroup\$
    – Phroggyy
    Nov 19, 2015 at 18:29
  • \$\begingroup\$ while($b) works just as well. \$\endgroup\$
    – frnhr
    Nov 21, 2015 at 18:32
  • \$\begingroup\$ There is an extra newline at the end though. \$\endgroup\$
    – frnhr
    Nov 21, 2015 at 18:33
  • \$\begingroup\$ Thanks for the whole suggestion! The newline is required though, to have a line in between verses \$\endgroup\$
    – Phroggyy
    Nov 21, 2015 at 18:41
4
\$\begingroup\$

Oracle SQL, 393 326 320 306 324 bytes*l

Grew a few bytes to fix error noted by C.L.

select b||decode(l,0,c||d||', '||b||c||'. Go to the store and buy some more, 99 bottle','s'||c||d||', '||b||'s'||c||'. Take one down and pass it around, '||l||' bottle')||decode(l,1,'','s')||c||d||'.'
from(select level-1 l,level||' bottle' b,' of beer' c,' on the wall' d from dual connect by level <= 99)order by l desc;

\$\endgroup\$
3
  • \$\begingroup\$ Second-to-last line ends with "1 bottles of beer". \$\endgroup\$
    – CL.
    Nov 22, 2015 at 9:59
  • \$\begingroup\$ Fixed - thanks for the catch on that. \$\endgroup\$ Nov 23, 2015 at 15:13
  • \$\begingroup\$ if you want to be on the leaderboard, please make your header be a markdown heading #Language Name \$\endgroup\$
    – cat
    Nov 27, 2015 at 16:10
4
\$\begingroup\$

x86 MS-DOS .COM file, 176 bytes

Advantages:

  • bitwise manipulation allows efficient encoding (However, in the end I settled on a relatively unpacked encoding which is pretty readable, just using the 8th bit of each byte to store spaces. I tried denser encodings that got the data to under 70 bytes, but the program size went up just as much.)
  • .COM files are widely supported from MS-DOS 1 through the present (I'm running in dosemu, sticking with 8086 commands only)

Disadvantages:

  • conditional jumps can get real obfuscated real quickly
  • some simple tasks are hard; it takes ~20 bytes to print the number of bottles

Hex dump (pass this into xxd -r to regenerate the 176-byte file):

0100: be a8 01 bd 63 00 40 75 06 4d 74 f7 be a4 01 8a  [email protected].....
0110: 0c 46 8a 1c 46 80 fb 89 75 18 89 e8 b7 0a f6 f7  .F..F...u.......
0120: b7 01 05 30 30 3c 30 92 b4 02 74 02 cd 21 88 f2  ...00<0...t..!..
0130: cd 21 38 2f 79 04 b2 20 cd 21 b2 7f 22 17 4d 75  .!8/y.. .!..".Mu
0140: 05 80 fb 8f 74 02 cd 21 45 43 e2 c9 81 fe ae 01  ....t..!EC......
0150: 76 b5 c3 54 61 6b 65 ef 6e 65 e4 6f 77 6e e1 6e  v..Take.ne.own.n
0160: 64 f0 61 73 73 e9 74 e1 72 6f 75 6e 64 47 6f f4  d.ass.t.roundGo.
0170: 6f f4 68 65 f3 74 6f 72 65 e1 6e 64 e2 75 79 f3  o.he.tore.nd.uy.
0180: 6f 6d 65 ed 6f 72 65 2c 20 e2 6f 74 74 6c 65 73  ome.ore, .ottles
0190: ef 66 e2 65 65 72 ef 6e f4 68 65 f7 61 6c 6c 2e  .f.eer.n.he.all.
01a0: 0d 0a 0d 0a 1a 53 1d 87 16 89 0f 87 03 9f 33 6d  .....S........3m

Explanations:

Bytes 0100 through 0152 (83 bytes) are commands, unassembled using debug:

0100 BEA801   MOV SI,01A8     ; point to 3rd substring
0103 BD6300   MOV BP,0063     ; begin with 99 bottles
0106 40       INC AX          ; only used to unset zero flag after last bottle taken
0107 7506     JNZ 010F        ; {when referred from 0150...if finished 5th substring:
0109 4D       DEC BP          ;    take down a bottle
010A 74F7     JZ 0103         ;    took last bottle, reset 99 bottles (and proceed to 6th)
010C BEA401   MOV SI,01A4     ;    or if not last bottle, point to 1st substring}
010F 8A0C     MOV CL,[SI]     ; load counter for substring
0111 46       INC SI
0112 8A1C     MOV BL,[SI]     ; load start pointer for substring
0114 46       INC SI
0115 80FB89   CMP BL,89       ; {  {check location to print # of bottles,
0118 7518     JNZ 0132        ;        if so...
011A 89E8     MOV AX,BP
011C B70A     MOV BH,0A
011E F6F7     DIV BH          ;        divide by 10
0120 B701     MOV BH,01       ;  (leave BH=1 for rest of program so [BX] points correctly)
0122 053030   ADD AX,3030     ;        convert to ascii numerals    
0125 3C30     CMP AL,30       ;        {if tens digit > 0,
0127 92       XCHG AX,DX
0128 B402     MOV AH,02       ;  (leave AH=2 for rest of program to print to stdout)
012A 7402     JZ 012E         
012C CD21     INT 21          ;           print tens digit}
012E 88F2     MOV DL,DH
0130 CD21     INT 21          ;        print ones digit}
0132 382F     CMP [BX],CH     ;    {check if character's 8th bit is set
0134 7904     JNS 013A        ;       if so...
0136 B220     MOV DL,20
0138 CD21     INT 21          ;       print " "}
013A B27F     MOV DL,7F
013C 2217     AND DL,[BX]     ;    load character without 8th bit
013E 4D       DEC BP          ;    {temporarily check if --bottle count = 0
013F 7505     JNZ 0146
0141 80FB8F   CMP BL,8F       ;       if so, check if at location of "s" to skip
0144 7402     JZ 0148
0146 CD21     INT 21          ;       print (unless skipping "s")
0148 45       INC BP          ;       undo temporary check}
0149 43       INC BX
014A E2C9     LOOP 0115       ;    proceed to next encoded character of substring}

014C 81FEAE01 CMP SI,01AE     ; check if we just finished the 5th of the 6 substrings
0150 76B5     JBE 0107        ; if the 5th or less, triage further at line 0107
0152 C3       RET             ; end after finishing 6th substring

Bytes 0153 through 01a3 (81 bytes) are the pretty readable encoded text

Bytes 01a4 through 01af (12 bytes) are (length,start) pairs pointing to substrings
    "Take...around"
    ", #bottles...wall.CRLFCRLF"
    "#bottles...wall"
    ", #bottles...beer"
    ".CRLF"
    "Go...wall.CRLF"
\$\endgroup\$
4
\$\begingroup\$

PARI/GP, 181 bytes

for(i=1,99,{G=Str("Take one down and pass it around, "b=Str(a=Str(i" bottl"e" of beer")" on the wall")".

"g=Str(b", "a".
"G));e=es);print(g"o to the store and buy some more, "b".")

Using the variable name G conveniently saves a byte. The closing brace doesn't seem to be required to end a multi-line context, strangely.

The shortest solution I know of previous to this is 32 bytes heavier.

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

R, 267 bytes

Not the shortest solution out there for R but it uses a different approach (namely dataframes).

d=data.frame(i=1:99,j=c(99,1:98));d$b=" bottles of beer";d$t="Take one down and pass it around";d[1,3:4]=c(" bottle of beer","Go to the store and buy some more");d$B=d$b[d$j];w=" on the wall";cat(rev(with(d,sprintf("\n%i%s%s, %i%s.\n%s, %i%s%s.",i,b,w,i,b,t,j,B,w))))

Indented, with newlines:

d = data.frame(i=1:99,j=c(99,1:98))
d$b = " bottles of beer"
d$t = "Take one down and pass it around"
d[1,3:4] = c(" bottle of beer","Go to the store and buy some more")
d$B = d$b[d$j]
w = " on the wall"
cat(rev(with(d,sprintf("\n%i%s%s, %i%s.\n%s, %i%s%s.",i,b,w,i,b,t,j,B,w))))

Here it is on ideone.

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

C Preprocessor, 753 bytes

#ifndef T
#define N
#define T 9
#define G(A,B) I(A,B)
#define I(A,B) A##B
#define X Take one down and pass it around,
#define Y G(bottle,s) of beer
#define Z Y on the wall
#define E(x) G(T,x) Z, G(T,x) Y.
#define F(x) X G(T,x) Z.
#endif
E(9)
F(8)
N
E(8)
F(7)
N
E(7)
F(6)
N
E(6)
F(5)
N
E(5)
F(4)
N
E(4)
F(3)
N
E(3)
F(2)
N
E(2)
#if T+0
F(1)
N
E(1)
F(0)
N
E(0)
#if T<2
#undef T
#define T
#elif T<3
#undef T
#define T 1
#elif T<4
#undef T
#define T 2
#elif T<5
#undef T
#define T 3
#elif T<6
#undef T
#define T 4
#elif T<7
#undef T
#define T 5
#elif T<8
#undef T
#define T 6
#elif T<9
#undef T
#define T 7
#else
#undef T
#define T 8
#endif
F(9)
N
#include __FILE__
#else
#define s
X 1 Z.
N
1 Z, 1 Y.
#undef s
Go to the store and buy some more, 99 Z.
#endif

Try it online!

It is my privilege to submit this C preprocessor solution to this catalog. Note that this is a pure C Preprocessor solution (or, as I prefer to call it, a C Preprocessor solution). To my knowledge this is the first such solution to be posted to a catalog (there are a few claimed "C preprocessor" solutions around, but they're really things like C programs using the C preprocessor to generate strings).

5 bytes could be saved if I exploited the file name (e.g. changing #include __FILE__ to #include "f").

If anyone can help golf this further let me know.

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

Japt, 157 127 bytes

1oL w £A=X+` Þ­¤{X-1?'s:P}  Þ8`;B=A+`  e Ø!` +".

{B}, {A}.
{´X?`Take e ܵ d ps  ÂÐ`:`Go  e ÐJe d ¿y Ñ  Ú`}, "ìéHn

Markdown hates unprintables (unlike the shoco string compression library), so here's a hexdump:

00000000: 316f 4c20 7720 a341 3d58 2b60 20de ada4 7b58    1oL w £A=X+` Þ.¤{X
00000012: 2d31 3f27 733a 507d 208f 20de 3860 203d 412b    -1?'s:P} . Þ8` =A+
00000024: 6020 8d20 9065 20d8 2160 3b42 2b22 2e0a 0a7b    ` . .e Ø!`;B+"...{
00000036: 427d 2c20 7b41 7d2e 0a7b b458 3f60 5461 6b65    B}, {A}..{´X?`Take
00000048: 208d 6520 dcb5 2084 6420 7086 7320 8a20 c2d0     .e ܵ .d p.s . ÂÐ
0000005A: 9860 3a60 476f 2091 2090 6520 d04a 6520 8464    .`:`Go . .e ÐJe .d
0000006C: 20bf 7920 d120 20da 9060 7d2c 2022 c3ac e948     ¿y Ñ  Ú.`}, "ìéH
0000007E: 6e                                              n

Try it online!

This could probably be made shorter by saving the repeated sections to variables. Absolutely correct, past me. Japt is now the shortest language without a built-in!

\$\endgroup\$
2
  • \$\begingroup\$ Seems to be missing the empty line between verses. \$\endgroup\$
    – primo
    Feb 23, 2017 at 14:43
  • \$\begingroup\$ @primo Thanks, fixed at the cost of one byte. \$\endgroup\$ Feb 24, 2017 at 13:04
4
\$\begingroup\$

T-SQL, 671 653 517 Bytes

Saw an Oracle one so I thought of doing it but I went for a cursor... Not competition level but still a lot of fun =)

DECLARE @q CHAR(15) = 'bottles of beer',@w CHAR(11)='on the wall',@e CHAR(33)='Take one down and pass it around,',@r INT=99,@i INT=1,@ INT=99 WHILE(@>=1)BEGIN SET @i=@-1 IF(@>1)BEGIN PRINT CONVERT(VARCHAR(2),@)+' '+@q+' '+@w+', '+CONVERT(VARCHAR(2),@)+' '+@q+'.'+CHAR(10)+@e+' '+CONVERT(VARCHAR,@i)+' '+@q+' '+@w+'.'+CHAR(13)END ELSE BEGIN PRINT CONVERT(VARCHAR(1),@)+' bottle of beer '+@w+', '+CONVERT(VARCHAR(1),@)+' bottle of beer.'+CHAR(10)+'Go to the store and buy some more, 99 '+@q+' '+@w+'.'END SET @=@-1 END

Proper indent:

DECLARE @q CHAR(15) = 'bottles of beer',
 @w CHAR(11) = 'on the wall',
 @e CHAR(33) = 'Take one down and pass it around,',
 @r INT = 99,
 @i INT = 1,
 @ INT = 99
WHILE (@ >= 1)
    BEGIN
    SET @i = @ -1
    IF (@ > 1)
        BEGIN
        PRINT
            CONVERT(VARCHAR(2), @)+' '+@q+' '+@w+', '+CONVERT(VARCHAR(2), @)+' '+@q+'.'+CHAR(10)+
            @e+' '+CONVERT(VARCHAR, @i)+' '+@q+' '+@w+'.'+CHAR(13)
        END
    ELSE
        BEGIN
        PRINT
            CONVERT(VARCHAR(1), @)+' bottle of beer '+@w+', '+CONVERT(VARCHAR(1), @)+' bottle of beer.'+CHAR(10)+
            'Go to the store and buy some more, 99 '+@q+' '+@w+'.'
        END
    SET @ = @-1
    END
\$\endgroup\$
3
  • \$\begingroup\$ Nice work. Quite a few places to save bytes: #1 Use CONCAT() instead of +, it implicitly casts the integers and you don't need CONVERT. #2: Eliminate @r (unused) and @i (just use @-1), and remove spaces around =. #3: String literals can include returns, use that instead of '+CHAR(10)+'. #4: This is more work but consider a label and a GOTO instead of a WHILE loop. #5: Include spaces at the beginning of @q and @w, and a (period return) at the beginning of @e. \$\endgroup\$
    – BradC
    Aug 9, 2017 at 21:05
  • \$\begingroup\$ Today I learned: @ is a valid variable name in T-SQL. Have a +1. \$\endgroup\$
    – primo
    Oct 22, 2017 at 9:03
  • 1
    \$\begingroup\$ @primo :) also: @@, @@@, @@@@... \$\endgroup\$
    – Nelson
    Oct 22, 2017 at 11:49
4
\$\begingroup\$

Python, 245 223 bytes

w="bottles of beer on the wall"
[print(i,w+',',w[:15]+'.\nTake one down and pass it around,',str(i-1),w+'.\n')for i in range(99,1,-1)]
print('1 bottle',w[8:],',1 bottle',w[8:15]+'.\nGo to the store and buy some more,',99,w)

edit: thank you GamrCorps for the suggestion to cut down over 20 bytes!

\$\endgroup\$
1
  • 3
    \$\begingroup\$ Welcome to PPCG.SE! Nice first post! There are some simple golfing improvements such as single-character variables and the starting index for the slicing (wall[0:8] can be wall[:8]). Otherwise, great job, I hope to see you around! \$\endgroup\$
    – GamrCorps
    Oct 22, 2017 at 20:15
4
\$\begingroup\$

Lua 5.1, 211 207 bytes

(Also works in Lua 5.2 and Lua 5.3.)

4 bytes saved thanks to primo.

B=" on the wall"P=""S="Go to the store and buy some more"for i=1,99 do
A=i.." bottle"..P.." of beer"S=([[Take one down and pass it around, AB.

AB, A.
S]]):gsub(".",_G)P="s"end
print(S:sub(68)..S:sub(33,65))

I chop up the song into chunks that have the same number of bottles of beer which simplifies the interpolation at the cost of some slicing at the end. This also allows constructing the verses backwards which simplifies the logic for pluralizing "bottle".

:gsub(".",_G) is a fun little call that replaces each character in a string with its value as a global variable (with no replacement if the global variable is not defined).

I played around with some of the tricks seen in the Python answers, but I wasn't able to beat this yet. It may still be possible to do a better in-order solution though.

Try it online

\$\endgroup\$
1
  • 1
    \$\begingroup\$ You can initialize S to S='Go to the store and buy some more'. Otherwise nice solution. The gsub on _G is brilliant. \$\endgroup\$
    – primo
    Dec 17, 2018 at 17:29
4
\$\begingroup\$

Bash, 167 bytes

Saved 2 bytes due to Sisyphus.

eval a={1..99}'" bottle$s of beer"
b="$a on the wall";f=$b,\ $a.$o;o="
Take one down and pass it around, $b.

$f";s=s';echo "$f
Go to the store and buy some more, $b."

Try it online!

\$\endgroup\$
2
  • \$\begingroup\$ 'a="' can become a=\" for a byte saved. \$\endgroup\$
    – Sisyphus
    Jan 8, 2022 at 10:45
  • 1
    \$\begingroup\$ Actually, even better you can just replace the whole construction with eval a={1..99}'" bottle$s of beer" \$\endgroup\$
    – Sisyphus
    Jan 8, 2022 at 10:48
3
\$\begingroup\$

PHP, 274 269 bytes

<?php $a=' bottles';for($i=99;$i>0;$i--){$d="Take one down and pass it around, ".($i-1)."$a<br><br>";if($i==1){$a=rtrim($a,'s');$d="Go to the store and buy some more, 99 bottles";}$d.=" of beer on the wall.";$s.="$i$a of beer on the wall, $i$a of beer.<br>".$d;}echo$s;
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Depending on you few output, <br> could be replaced by \n, and you can replace rtrim() with trim() as there is no s in front of 'bottles' \$\endgroup\$
    – Martijn
    Nov 18, 2015 at 21:22
3
\$\begingroup\$

Pyth, 157 156 150 149 148 145 bytes

L+b%" bottle%s of beer on the wall."<\stbVK99s[PyKJ", "<13yK\.)++?=tK"Take one down and pass it around""Go to the store and buy some more"Jy|K99k

Try it online.

\$\endgroup\$
1
  • \$\begingroup\$ You can shorten this a bunch using the new ." operator. I can't seem to get the online compiler to make a friendly link to a working one, but here is a paste of the hexdump. I'm not sure if it is a bug, but I couldn't get ." to work with a leading space, but I believe it still saves one byte. \$\endgroup\$ Nov 19, 2015 at 16:20
3
\$\begingroup\$

Perl 5, 243 bytes

A shorter Perl 5 solution is posted as a community wiki solution here.

$}=" bottle";$s='s';$o=" of beer";$w=" on the wall";$i=99;while($i>1){say"$i$}$s$o$w, $i$}$s$o.";$s=''if--$i<2;say"Take one down and pass it around, $i$}$s$o$w.\n"}say"$i$}$s$o$w, $i$}$s$o.";say"Go to the store and buy some more, 99$}s$s$o$w."

Using the free -M5.010 switch for "say" instead of "print".

Cheers.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ I think it'd be cheaper to use last if$i<2; before the $s assignment (and change the while to while($i)) than to have the extra say in the second-to-last line. \$\endgroup\$
    – msh210
    Nov 18, 2015 at 21:59
  • 2
    \$\begingroup\$ (Oh, and there's a shorter version at 99-bottles-of-beer.net/language-perl-727.html. :-)) \$\endgroup\$
    – msh210
    Nov 18, 2015 at 22:04
  • \$\begingroup\$ About that link. It doesn't go to the store, as the challenge requests. But thanks for the hint, the different methods used are interesting. \$\endgroup\$
    – LukStorms
    Nov 18, 2015 at 22:15
3
\$\begingroup\$

C#, 274 bytes

Direct copy from my answer on another question (it was popularity, but I just treated it as golf). Never did get around to golfing it further.

class 
P{static 
void Main(){
for(int i=99;i>0;
)System.Console.Write
(i+"{1}{2}{3}, {0}{1}{2} of beer.\n"+(i>1?
"Take one down and pass it around, {4}{1}{5}{3}.\n\n":
"Go to the store and buy some more, 99{1}s{3}."
),i--," bottle",i>0?"s":"",
" of beer on the wall"
,i,i>1?"s":""
);}}
\$\endgroup\$
3
  • \$\begingroup\$ Nice one! Why you indent like this instead of one line? \$\endgroup\$
    – aloisdg
    Jul 7, 2016 at 22:50
  • \$\begingroup\$ Nevermind I guess it is just for reading. \$\endgroup\$
    – aloisdg
    Jul 7, 2016 at 23:04
  • 2
    \$\begingroup\$ @aloisdg I think I made a weak attempt to shape the code in some fun way (like an arrow), but failed. \$\endgroup\$ Jul 11, 2016 at 6:41
3
\$\begingroup\$

PHP, 251 bytes

This accounts for the plural/singular problem.

<?php for($i=99;$i>0;$i--){$b=" of beer";$s=" bottles$b";$r=" bottle$b";$w=" on the wall";$h=$i-1;echo$h>=1?"$i$s$w, $i$s.\nTake one down and pass it around, $h".($h<2?$r:$s)."$w.\n\n":"$i$r$w, $i$r. \nGo to the store and buy some more, 99$s$w.\n\n";}

Readable:

for ($i=99; $i > 0; $i--) {
  $b = " of beer";
  $s = " bottles$b";
  $r = " bottle$b";
  $w = " on the wall";
  $h = $i - 1;
  echo $h >= 1 ? "$i$s$w, $i$s.\nTake one down and pass it around, $h" . ($h<2 ? $r : $s) . "$w.\n\n" : "$i$r$w, $i$r. \nGo to the store and buy some more, 99$s$w. \n\n";
}
\$\endgroup\$
2
3
\$\begingroup\$

Perl 5, 159

@c=(@b=(++$n,bottle.'s'x@-,of,beer),on,the,wall),s//Take one down and pass it around, @c.

@c, @b.
/,until/99\D+/;say$'."Go to the store and buy some more, $&"

Based on the solution from eyepopslikeamosquito found here.

Requires the free -M5.010 switch to use "say".

\$\endgroup\$
0
3
\$\begingroup\$

05AB1E, 167 bytes

Dammit, 167 is waaayyy too long. But this gave me some new ideas to make this a bit shorter.

Code:

99L`[D?ð?“±¥“?D1Q"s "si\ð}?“€‚¬ž€‰€€íÒ, “??ð?“±¥“?D1Q"s "si\ð}?“€‚¬ž.“,Ž“Ÿ†€µ§€€ƒî倕³†, “ª?D?ð?“±¥“?D1Q"s "si\ð}?“€‚¬ž€‰€€íÒ.
“,]“‚œ€„€€›‰€ƒŽŠ€ä€£, 99Ïꀂ¬ž€‰€€íÒ.“ª,

Try it online!

Uses CP-1252 encoding.

\$\endgroup\$
3
\$\begingroup\$

PHP, 226 221 bytes

Minified

<? $w=" on the wall";for($i=99;$i>=0;$i--){if($j&&$i)echo"Take one down and pass it around, $i$j$w.

";$j=" bottle".($i!=1?'s':'')." of beer";if($i)echo"$i$j$w, $i$j.
";}echo"Go to the store and buy some more, 99$j$w.";

Expanded

<?

  $w=" on the wall";

  for($i=99;$i>=0;$i--){
    if($j && $i)
       echo "Take one down and pass it around, $i$j$w.

";

    $j=" bottle".($i!=1?'s':'')." of beer";

    if($i)
       echo "$i$j$w, $i$j.
";
  }

  echo "Go to the store and buy some more, 99$j$w.";

https://ideone.com/m89z3E

\$\endgroup\$
6
  • 1
    \$\begingroup\$ We allow the use of the short opening tag (<?) here, though my memory is slipping as to whether or not that requires another command-line argument (and thus only saving 2 bytes rather than 3). Welcome to PPCG! \$\endgroup\$
    – user45941
    Feb 24, 2016 at 9:25
  • 1
    \$\begingroup\$ @mego Short open tags are enabled by default (i.e. if there were no .ini at all) and therefore, at least in my option, should also be allowed by default. relevant meta post \$\endgroup\$
    – primo
    Feb 24, 2016 at 10:09
  • \$\begingroup\$ @primo That's right, thanks for refreshing my memory. \$\endgroup\$
    – user45941
    Feb 24, 2016 at 10:10
  • \$\begingroup\$ thanks for the tips! it seems that many of the php answers here include the full tags, so in the spirit of consistency, I'll leave it. But thanks for posting the meta because I wasn't sure about notices (i.e. $j is uninitialized) but it seems they're ok \$\endgroup\$
    – FuzzyTree
    Feb 24, 2016 at 17:31
  • 1
    \$\begingroup\$ A good way to check is to run the script with the -n option to disable the local ini: php -n script.php. If it produces the correct output, it should be considered valid. While I'm here, you can replace the \n with a literal new line to save a few bytes. You might also be able to combine the last two echo statements with a ternary, e.g. echo$i?"string if true":"string if false";. \$\endgroup\$
    – primo
    Feb 25, 2016 at 2:02
3
\$\begingroup\$

T-SQL, 436 323 bytes

WITH _ AS(SELECT 99B UNION ALL SELECT B-1 FROM _ WHERE B>2)SELECT REPLACE(REPLACE(REPLACE(A,'#',' of beer'),'@',' on the wall'),'!',' bottle')FROM(SELECT REPLACE(REPLACE('^!s#@, ^!s#.
Take one down and pass it around, %!s#@.','^',B),'%',B-1)A FROM _ UNION ALL SELECT'1!#@, 1!#.
Go to the store and buy some more, 99!s#@.')A
\$\endgroup\$
7
  • \$\begingroup\$ you can save a few more by removing a couple of spaces SELECT 99B and ,B-1)FROM. Also since ` bottles of beer` is repeated a few times a replace on that will trim a few more. \$\endgroup\$
    – MickyT
    May 20, 2016 at 2:24
  • \$\begingroup\$ Some of the replacements aren't going to save anything or will actually cause it to expand since replace is so many characters... I an working on using the XML contact hack to see if I can reduce it more. \$\endgroup\$ May 20, 2016 at 2:29
  • \$\begingroup\$ not sure I can save enough characters to just break even on the XML hack :( \$\endgroup\$ May 20, 2016 at 2:41
  • \$\begingroup\$ fixed and added 4 bytes :( \$\endgroup\$ May 20, 2016 at 3:36
  • \$\begingroup\$ Using this your byte count is 338. single byte for the line feeds \$\endgroup\$
    – MickyT
    May 20, 2016 at 3:47
3
\$\begingroup\$

Scratch, 252 bytes

Script
(scoring used)
Resets, repeats for 98 cycles, then a modified last block for the last line.

If you allow "1 bottles" in the list, this can be golfed down to 133 bytes.
Script Alt

\$\endgroup\$
3
  • \$\begingroup\$ Nice Scratch answers! \$\endgroup\$
    – Riker
    May 20, 2016 at 13:55
  • \$\begingroup\$ Thanks, I've been warming up to it after leaving it for a year. \$\endgroup\$ May 20, 2016 at 14:03
  • \$\begingroup\$ @weatherman115 I think mine is better. Also, your output is incorrect; it's one line, the output needs to be two lines per verse. Not very golfed either \$\endgroup\$
    – anna328p
    Jul 7, 2016 at 17:49
3
\$\begingroup\$

Retina, 159 bytes

Byte count assumes ISO 8859-1 encoding. The leading linefeed is significant.


99$*1 bottles of beer
1
1$'W, 1$'.¶Take one down and pass it around, $'W.¶¶
1+
$.&
G-3`
W
 on the wall
T`s``\b1 .+
.+ \B
Go to the store and buy some more, 99

Try it online!

I can't be believe I (or anyone else) have ever done this in Retina...

Explanation


99$* bottles of beer

We start by replacing the empty input with 99 1s followed by bottles of beer.

1
1$'W, 1$'.¶Take one down and pass it around, $'W.¶¶

Now we replace each of those 1s with the substitution pattern on the second line. Here $' stands for the string after each match, and we use W as a placeholder for later. Note that the occurrences of $' on the first line are "incremented" by prepending a 1. We've got most of the work done already, except that the numbers are in unary instead of decimal, and we've got Ws instead of on the walls and the last verse is off.

1+
$.&

This does the unary-to-decimal conversion by matching each run of 1s and replacing it with its length.

G-3`

This is a "grep" stage. The regex is empty, so it always matches, but the -3 limit means that only lines up to the third from the end are kept, so the last two are discarded. This gets rid of the two extraneous linefeeds at the end.

W
 on the wall

Now we substitute our W placeholder. At this point, we've got everything in place except that the last verse is still wrong.

T`s``\b1 .+

This fixes the pluralisation by removing all ss from the match of the regex at the end, which matches anything after a 1 (on the same line).

Note that the last line doesn't have 0 since the unary representation of it is an empty string it wasn't matched by the conversion stage. Instead there will be two spaces in a row.

.+ \B
Go to the store and buy some more, 99

This is the only case where there is no word boundary after a space, so we detect the line like this and replace it with the correct lyrics.

\$\endgroup\$
2
3
\$\begingroup\$

MIT Scratch 2.0, 184 bytes

Notes: Since this language is graphical, I counted each block/element used, each byte of the strings, and variable invocations as a byte.

Screenshot

Try it online: https://scratch.mit.edu/projects/115797194/#player

Notes: This program does not automatically clear the list after it's run, you have to go to the editor and run the following block:

clear list block

I have conveniently added it into the project, however it is not part of the program and does not count towards the byte count.

\$\endgroup\$
3
\$\begingroup\$

Ruby, 237 (thanks primo!) 227 bytes

99.downto(2){|i|puts "#{i}#{$B=" bottles of beer"}#{$W=" on the wall"}, #{i}#$B,\nTake one down and pass it around, #{i-1}#{i<3?$C=$B.tr('s','') :$B}#$W.\n\n"};puts "1#$C#$W, 1#$C,\nGo to the store and buy some more, 99#$B#$W."

Mostly ungolfed: (old, outdated)

$B="bottles of beer"
$C="bottle of beer"
$W="on the wall"
99.downto(2) { |i|
  puts "#{i} #$B #$W, #{i} #$B,\nTake one down and pass it around, #{i-1} #{i==2 ? $C : $B} #$W.\n\n"}
  puts "1 #$C #$W, 1 #$C,\nGo to the store and buy some more, 99 #$B #$W."

Try it online:

http://codepad.org/ozIpcgPI

\$\endgroup\$
3
  • \$\begingroup\$ Save a few bytes my moving each string definition to its first use: #{i}#{$B=' bottles of beer'}#{$W=' on the wall'}, and #{i-1}#{i==2?$C=' bottle of beer':$B}. \$\endgroup\$
    – primo
    Jul 10, 2016 at 6:51
  • 1
    \$\begingroup\$ Alternatives for $C: $C=$B.tr('s',''), or assuming you rename $B to $_: ~/s/;$C=$`+$'. \$\endgroup\$
    – primo
    Jul 10, 2016 at 7:07
  • \$\begingroup\$ Also i<3 instead of i==2 and you can put actual linefeeds in your strings instead of using the \n escape sequence. \$\endgroup\$ Jul 10, 2016 at 9:32
2
\$\begingroup\$

C++, 289 bytes

Golfed:

#include <cstdio>
void main(){char*s=" bottles of beer",*t=" on the wall",*u=" bottle of beer";for(int i=99;i>1;--i)printf("%i%s%s, %i%s.\nTake one down and pass it around, %i%s%s.\n\n",i,s,t,i,s,i-1,i<3?u:s,t);printf("1%s%s, 1%s.\nGo to the store and buy some more, 99%s%s.",u,t,u,s,t);}

Works in Microsoft Visual Studio. Though some compilers may require that void main be changed to int main and a return value provided. This increases the size to 297:

#include <cstdio>
int main(){char*s=" bottles of beer",*t=" on the wall",*u=" bottle of beer";for(int i=99;i>1;--i)printf("%i%s%s, %i%s.\nTake one down and pass it around, %i%s%s.\n\n",i,s,t,i,s,i-1,i<3?u:s,t);printf("1%s%s, 1%s.\nGo to the store and buy some more, 99%s%s.",u,t,u,s,t);return 0;}

Ungolfed:

#include <cstdio>

void main()
{
    char* s = " bottles of beer";
    char* t = " on the wall";
    char* u = " bottle of beer";

    for(int i = 99; i > 1; --i)
    {
        printf("%i%s%s, %i%s.\nTake one down and pass it around, %i%s%s.\n\n", i, s, t, i, s, (i - 1), (i < 3 ? u : s), t);
    }

    printf("1%s%s, 1%s.\nGo to the store and buy some more, 99%s%s.", u, t, u, s, t);
}

Link to demo.

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

Mouse, 244 bytes

99N:(N.0>^N.!" bottle"N.1>["s"]" of beer on the wall, "N.!" bottle"N.1>["s"]" of beer.!"N.1>["Take one down and pass it around"]N.1=["Go to the store and buy some more"]", "N.1-N:N.0=[99]N.0>[N.]!" bottle"N.1=0=["s"]" of beer on the wall.!!")$

Mouse has very limited capabilities in general, though this is particularly apparent in its handling of strings. The stack can contain only integers; any quoted strings encountered are printed to STDOUT immediately. Further, there is no else construct, so each condition must be stated explicitly.

Ungolfed:

99 N:                                  ~ Initialize a counter
( N. 0 > ^                             ~ While N > 0...
  N. ! " bottle" N. 1 > ["s"]          ~ Print N, conditionally pluralize
  " of beer on the wall, "
  N. ! " bottle" N. 1 > ["s"]
  " of beer.!"
  N. 1 > ["Take one down and pass it around"]
  N. 1 = ["Go to the store and buy some more"]
  ", "
  N. 1 - N:                            ~ Decrement the counter
  N. 0 = [99] N. 0 > [N.] !
  " bottle" N. 1 = 0 = ["s"]
  " of beer on the wall.!!"            ~ ! inside a string is \n
)$
\$\endgroup\$
2
\$\begingroup\$

DStack, 265 bytes

@0
# bottles of beer on the wall, # bottles of beer.
Take one down and pass it around, $ bottles of beer on the wall.


@
@1
1 bottle of beer on the wall, 1 bottle of beer.
Go to the store and buy some more, 99 bottles of beer on the wall.
@
01SSd99CCcscDtTasd9ttkt
\$\endgroup\$
2
\$\begingroup\$

PHP, 267 bytes

<?php $i=99;$t="Take one down and pass it around, ";$b=" bottles of beer";$o=" on the wall";echo"99$b$o, 99$b.\n";while(--$i>1)echo"$t$i$b$o.\n\n$i$b$o, $i$b.\n";$B=str_replace('s','',$b);echo"$t$i$B$o.\n\n$i$B$o, $i$B.\nGo to the store and buy some more, 99$b$o.";

Readable version, with comments:

<?php

$i=99;
$t="Take one down and pass it around, ";
$b=" bottles of beer";
$o=" on the wall";

// first one
echo"99$b$o, 99$b.\n";

// decrement from 98 to 2
while(--$i>1) echo"$t$i$b$o.\n\n$i$b$o, $i$b.\n"; 

// ^^ "Take one down and pass it around, $i bottles of beer on the wall.\n\n ^^
// $i bottles of beer on the wall, $i bottles of beer.\n"

// last one
$B=str_replace('s','',$b); // change "bottles" to "bottle"
echo"$t$i$B$o.\n\n$i$B$o, $i$B.\nGo to the store and buy some more, 99$b$o.";
\$\endgroup\$
2
\$\begingroup\$

PHP, 248 bytes (with pluralisation)

$b=99;$x=function($n){return"$n bottle".($n!=1?'s':'')." of beer";};$y=" on the wall";do{echo"{$x($b)}$y, {$x($b)}.\n";echo--$b?"Take one down and pass it around":"Go to the store and buy some more";$b||$b=99;echo", {$x($b)}$y.\n\n";}while($b<99);

readable:

<?php

$b=99;
$x=function($n){return"$n bottle".($n!=1?'s':'')." of beer";};
$y=" on the wall";
do{
    echo"{$x($b)}$y, {$x($b)}.\n";
    echo--$b?"Take one down and pass it around":"Go to the store and buy some more";
    $b||$b=99;
    echo", {$x($b)}$y.\n\n";
}while($b<99);

NOTE: previoius version was 197 bytes but didnt correctly show singular "bottle". see history if interested

\$\endgroup\$
5
  • \$\begingroup\$ See whether string interpolation instead of concatenation would help: echo $b.$c.", ".$b.$d.".\n";echo "$b$c, $b$d.\n";. \$\endgroup\$
    – manatwork
    Nov 19, 2015 at 11:58
  • \$\begingroup\$ @manatwork: cool didnt think of that! \$\endgroup\$ Nov 19, 2015 at 12:07
  • \$\begingroup\$ Just some quick changes, trying to keep the base idea: $b=99;$d=" bottles of beer";$c="$d on the wall";do{echo"$b$c, $b$d.\n",--$b?"Take one down and pass it around":"Go to the store and buy some more";$b||$b=99;echo", $b$c.\n\n";}while($b<99);. (You can find more tricks to apply in Tips for golfing in PHP.) \$\endgroup\$
    – manatwork
    Nov 19, 2015 at 12:30
  • \$\begingroup\$ Sorry dude but at the end this still says "bottles" i.e. 1 bottles of beer on the wall - should be "bottle" \$\endgroup\$ Nov 19, 2015 at 12:36
  • \$\begingroup\$ There is an extra newline at the end. \$\endgroup\$
    – frnhr
    Nov 21, 2015 at 18:26
2
\$\begingroup\$

Go, 287

package main
import."fmt"
func main(){p,s,n:=Printf,"%[1]d bottle%[2]s of beer",`.
`
o:=s+" on the wall"
a,b,c,i,k:=o+`, `+s+n,`Take one down and pass it around, `+o+n+`
`,`Go to the store and buy some more, `+o+n,99,"s"
for 1<i{p(a,i,k)
if i--;i<2{k=""}
p(b,i,k)}
p(a,i,k)
p(c,99,"s")}

There is probably room for further optimization

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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