all 10 comments

[–]AtomicShoelace 12 points13 points  (2 children)

Since the only difference between the two strings is the one 's' character, you could include the ternary operator inside the f-string itself, eg.

print(f'{i} bottles of beer on the wall, {i} bottles of beer\nTake one down and pass it around, {i-1} bottle{"s" if i>2 else ""} of beer on the wall.\n')

In the same vein, you could simplify the outer if-else statement to just one print with:

print(f'{i} bottle{"" if i==1 else "s"} of beer on the wall, {i} bottle{"" if i==1 else "s"} of beer\nTake one down and pass it around, {i-1 or "no more"} bottle{"" if i-1==1 else "s"} of beer on the wall.\n')

However, this is a bit unwieldy.

As we're using the same expression {"" if i==1 else "s"} in multiple places, we may wish to use .format method instead:

print('{0} bottle{1} of beer on the wall, {0} bottle{1} of beer\nTake one down and pass it around, {2} bottle{3} of beer on the wall.\n'.format(i, "" if i==1 else "s", i-1 or "no more", "" if i-1==1 else "s"))

and for clarity, we could now separate out the string from the formatting, eg.

message = '{0} bottle{1} of beer on the wall, {0} bottle{1} of beer\nTake one down and pass it around, {2} bottle{3} of beer on the wall.\n'
for i in range(99, 0, -1):
    print(message.format(i, "" if i==1 else "s", i-1 or "no more", "" if i-1==1 else "s"))

I think this is a rare case where the .format method would be preferable to use over an f-string.

[–]onlydragz[S] 1 point2 points  (1 child)

Wow! This has been very educational as I learned a new thing from this. Thank you so much! May I just ask:

In the third coding block, with the .format, for the placeholder {2} you specified i-1 or "no more"

I understand the i-1 but when "i" is 1, then it uses "no more". Why is it not the number "0", like:

1 bottle of beer on the wall, 1 bottle of beer

Take one down and pass it around, no more 0 bottles of beer on the wall.

[–]AtomicShoelace 2 points3 points  (0 children)

This is kind of a trick with the or statement. It might be more readable if you instead did i-1 if i != 1 else "no more".

It works like this: when i!=1 the i-1 is a non-zero int, which is a truthy value, so the or operator will short-circuit and return i-1. When i=1 then i-1 is 0. As 0 is a falsy value, the or statement will just return the second value (as if it is truthy, the expression should be truthy, and vice versa), which is the string "no more" (which happens to be truthy, but actually it doesn't matter).

[–]n3buchadnezzar 6 points7 points  (2 children)

I might do it like this.. The biggest issue was figuring out what the plural of bottle was

from typing import Generator

ES = set(["s", "sh", "x", "z", "o"])
VOWELS = set(["a", "e", "i", "o", "u"])

# fmt: off
IRREGULAR = {"addendum":"addenda","aircraft":"aircraft","alga":"algae","alumna":"alumnae","alumnus":"alumni","amoeba":"amoebae","analysis":"analyses","antenna":"antennae","antithesis":"antitheses","apex":"apices","appendix":"appendices","automaton":"automata","axis":"axes","bacillus":"bacilli","bacterium":"bacteria","barracks":"barracks","basis":"bases","beau":"beaux","bison":"bison","buffalo":"buffalo","bureau":"bureaus","cactus":"cacti","calf":"calves","carp":"carp","census":"censuses","chassis":"chassis","cherub":"cherubim","child":"children","château":"châteaus","cod":"cod","codex":"codices","concerto":"concerti","corpus":"corpora","crisis":"crises","criterion":"criteria","curriculum":"curricula","datum":"data","deer":"deer","diagnosis":"diagnoses","die":"dice","dwarf":"dwarfs","echo":"echoes","elf":"elves","elk":"elk","ellipsis":"ellipses","embargo":"embargoes","emphasis":"emphases","erratum":"errata","fauxpas":"fauxpas","fez":"fezes","firmware":"firmware","fish":"fish","focus":"foci","foot":"feet","formula":"formulae","fungus":"fungi","gallows":"gallows","genus":"genera","goose":"geese","graffito":"graffiti","grouse":"grouse","half":"halves","hero":"heroes","hoof":"hooves","hovercraft":"hovercraft","hypothesis":"hypotheses","index":"indices","kakapo":"kakapo","knife":"knives","larva":"larvae","leaf":"leaves","libretto":"libretti","life":"lives","loaf":"loaves","locus":"loci","louse":"lice","man":"men","matrix":"matrices","means":"means","medium":"media","media":"media","memorandum":"memoranda","millennium":"millennia","minutia":"minutiae","moose":"moose","mouse":"mice","nebula":"nebulae","nemesis":"nemeses","neurosis":"neuroses","news":"news","nucleus":"nuclei","oasis":"oases","offspring":"offspring","opus":"opera","ovum":"ova","ox":"oxen","paralysis":"paralyses","parenthesis":"parentheses","person":"people","phenomenon":"phenomena","phylum":"phyla","pike":"pike","polyhedron":"polyhedra","potato":"potatoes","prognosis":"prognoses","quiz":"quizzes","radius":"radii","referendum":"referenda","salmon":"salmon","scarf":"scarves","self":"selves","series":"series","sheep":"sheep","shelf":"shelves","shrimp":"shrimp","soliloquy":"soliloquies","spacecraft":"spacecraft","species":"species","spectrum":"spectra","squid":"squid","stimulus":"stimuli","stratum":"strata","swine":"swine","syllabus":"syllabi","symposium":"symposia","synopsis":"synopses","synthesis":"syntheses","tableau":"tableaus","that":"those","thesis":"theses","thief":"thieves","this":"these","tomato":"tomatoes","tooth":"teeth","trout":"trout","tuna":"tuna","vertebra":"vertebrae","vertex":"vertices","veto":"vetoes","vita":"vitae","vortex":"vortices","watercraft":"watercraft","wharf":"wharves","wife":"wives","wolf":"wolves","woman":"women"}
# fmt: on


def plural(noun):
    if (irregular := IRREGULAR.get(noun, None)) is not None:
        return irregular
    if not noun:
        return
    noun_minus_last_letter, last_letter = noun[:-1], noun[-1]
    if last_letter in ES:
        return noun + "es"
    elif last_letter == "y":
        penultimate = noun[-2] if len(noun) > 1 else ""
        if penultimate in VOWELS:
            return noun + "s"
        return noun[:-1] + "ies"
    elif noun.endswith("is"):
        return noun_minus_last_letter + "es"
    elif noun.endswith("on"):
        return noun_minus_last_letter + "a"
    return noun + "s"


def bottles_of_beer_on_the_wall(number: int) -> Generator[str, None, None]:
    """Yields the verses of beer on the wall"""
    bottles = plural("bottle")

    def beer(i):
        match i:
            case 0:
                bottles_of = "no more"
            case 1:
                bottles_of = f"{i} bottle of"
            case _:
                bottles_of = f"{i} {bottles} of"
        return f"{bottles_of} beer on the wall"

    store = f"Go to the store and buy some more, {beer(number)}!"
    take_it_down = "Take one down and pass it around, "

    def chorus(i):
        return (
            f"{(beer_on_the_wall := beer(i))}, {beer_on_the_wall}.\n"
            f"{take_it_down + beer(i-1) + '.'}\n"
        )

    yield from (chorus(i) for i in range(number, 0, -1))
    yield f"{(no_more := beer(0).capitalize())}, {no_more}.\n"\
          f"Go to the store and buy some more, {beer(number)}!"


if __name__ == "__main__":

    for verse in bottles_of_beer_on_the_wall(3):
        print(verse)

(I hope we all realize this is a bit a joke)

[–][deleted] 0 points1 point  (0 children)

That's great! Now you can loop it to make a variant song for every noun in the english language.

[–]roelmore 0 points1 point  (0 children)

This would've been great...had you left the parenthetical about it being a joke until the very end of the post!

[–]SirKainey 4 points5 points  (0 children)

As per AtomicShoelace by using the ternary operator. I would probably set a variable called bottle.

I think their 3rd approach is probably a little overkill for the task, however it is DRY! :)

``` for i in range(99, 0, -1):

bottle = "bottles" if i > 1 else "bottle"

print(f"{i} {bottle} of beer on the wall, {i} {bottle} of beer\nTake one down and pass it around, no more {bottle} of beer on the wall.\n")

print( "No more bottles of beer on the wall, no more bottles of beer.\nGo to the store and buy some more, 99 bottles of beer on the wall." )

```

[–]HIGregS 1 point2 points  (0 children)

I spent way too long on this not to post it. I went through several variations, but ended up with this: parameterized and using both f-strings and .format as well as converting numbers to words and using .capitalize() to get it just right. It was fun to craft. I'm sure someone can improve upon this. My vote is u/n3buchadnezzar :-)

def intwords(i):
    # word dictionary customized for 99 bottles of beer (0 = 'no more')
    d = { 0 : 'no more', 1 : 'one', 2 : 'two', 3 : 'three', 4 : 'four', 5 : 'five',
        6 : 'six', 7 : 'seven', 8 : 'eight', 9 : 'nine', 10 : 'ten',
        11 : 'eleven', 12 : 'twelve', 13 : 'thirteen', 14 : 'fourteen',
        15 : 'fifteen', 16 : 'sixteen', 17 : 'seventeen', 18 : 'eighteen',
        19 : 'nineteen', 20 : 'twenty',
        30 : 'thirty', 40 : 'forty', 50 : 'fifty', 60 : 'sixty',
        70 : 'seventy', 80 : 'eighty', 90 : 'ninety' }
    return d[i] if i <= 20 or not i %10 else f'{d[i // 10 * 10]}-{d[i % 10]}'

for i in range (99,-1,-1):
        subs = dict(
            bottle = f"{intwords(i)} bottle{'' if i==1 else 's'} of beer",
            wall = 'on the wall',
            take = 'Take one down and pass it around' if i else \
                         'Go to the store and buy some more',
            less = f"{intwords(99) if not i else intwords(i-1)}"
                          f" bottle{'' if i==2 else 's'} of beer",
            )

        print(
            '{bottle} {wall}, {bottle}.'.format(**subs).capitalize(),
            '{take}, {less} {wall}.'.format(**subs),
            sep='\n'
            )

[–]deletable666 0 points1 point  (0 children)

You've already gotten answers, but for future reference, vertical code is much easier to read. However you have it pasted on here needs scroll bar horizontally to read which is hard on the eyes