Truthy and Falsey Values with f-strings

Raindrops
Raindrops in Python
def convert(number):
    threes = '' if number % 3 else 'Pling' # Empty string if modulo == 1 (0 evaluates to False)
    fives =  '' if number % 5 else 'Plang'
    sevens = '' if number % 7 else 'Plong'
    
    return f'{threes}{fives}{sevens}' or str(number)
  
  #OR#
  
def convert(number):
    threes = 'Pling' if not number % 3 else '' # Sound if NOT modulo == 0 
    fives =  'Plang' if not number % 5 else ''
    sevens = 'Plong' if not number % 7 else ''
    
    return f'{threes}{fives}{sevens}' or str(number)

This is very similar to the if-statement approach logic, but uses ternary expressions to assign either an empty string or a drop sound to a variable. The variables are then used in an f-string to compose the result, avoiding the use of join(), or a loop. If the f-string is empty (evaluating to False in a boolean context), a str of the input number is returned instead.

This has O(1) time and space complexity.

These two variations both exploit the fact that boolean True and False are a subtype of int in Python. 0 evaluates to False, and 1 to True. So the expression 'Pling' if not number % 3 else '' can be read as "return 'Pling" if number % 3 not False" where False is 0, and (not False) is 1. The expression '' if number % 3 else 'Pling' is the inverse: "return '' if number % 3 is True" - where number % 3 > 0 is True, and number % 3 == 0 is False.

Like the if-statement approach, these solutions are nicely readable and to-the-point, but will grow in length and get harder to read if many more factors are added or business logic changes. The f-string in particular could get unwieldy beyond about 5 factors. Other solutions using data structures to hold factors and join() to assemble strings might be a better option in 'high change' situations.

4th Dec 2024 · Found it useful?

Other Approaches to Raindrops in Python

Other ways our community solved this exercise
def convert(num):
    sounds = ''

    if num % 3 == 0: sounds += 'Pling'
    if num % 5 == 0: sounds += 'Plang'
    if num % 7 == 0: sounds += 'Plong'

    return sounds or str(num)
if statements

Use a series of if statements and string concatenation.

def convert(number):
    sounds = ''
    drops = ("i", 3), ("a", 5), ("o", 7)

    for vowel, factor in drops:
        if number % factor == 0:
            sounds += f'Pl{vowel}ng'
    return sounds or str(number)
Loop and f-string

Loop through a tuple and assemble output via an f-string.

def convert(number):
    drops = ["Pling","Plang","Plong"]
    factors = [3,5,7]
    sounds = ''.join(drops[index] for
                     index, factor in
                     enumerate(factors) if (number % factor == 0))

    return sounds or str(number)
Sequence(s) with str.join()

Use one or more sequences and str.join() with a generator-expression.

def convert(number):
    sounds = {3: 'Pling', 5: 'Plang', 7: 'Plong'}

    results = ''.join(sounds[divisor] for
                      divisor in sounds.keys()
                      if number % divisor == 0)

    return results or str(number)
Dict and str.join()

Use a dict to hold factors:values and str.join() with a generator-expression.

from itertools import compress

def convert(number):
    sounds ='Pling','Plang','Plong'
    mask =  ((number % factor) == 0 for factor in (3,5,7))
    return ''.join(compress(sounds, mask)) or str(number)
itertools.compress

Use itertools.compress() with a list mask.

from functools import reduce

def convert(number):
    sounds = ('Pling','Plang','Plong')
    factors = ((number % factor) == 0 for factor in (3,5,7))
    result = reduce(lambda sound, item : sound + (item[0] * item[1]), zip(sounds, factors), '')

    return result or str(number)
functools.reduce

Use functools.reduce() and zip().