itertools.compress

Raindrops
Raindrops in Python
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)

This approach uses both a tuple for storing sound strings and mask to filter them with. For each factor, the generator-expression calculates True (evenly divisible) or False (remainder left over). This mask is used with itertools.compress() to 'mask over' any unwanted values in 'sounds'. Finally, the returned string is created with str.join(). If the 'sounds' string is empty, a string version of the number is returned instead.

This is very succinct code that avoids string concatenation. However, it does require the overhead of importing compress() from the itertools module. The code is also harder to maintain should there be additional factors/sounds needed. Because the factors and sounds are separated, there is a chance mistakes could be made like forgetting a number or swapping which factor is paired with which sound.

A better approach for maintenance might be to turn the 'sounds' tuple into a dictionary where the factors and sounds can be stored separate from the logic that does the calculations and string creation:

from itertools import compress

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

In this rewrite, factors are keys with the sounds as values in a dictionary. The mask then uses a dict.keys() view to iterate over the factors and calculate the True/False values. This mask is used with compress() to filter a dict.values() view that is used by join() to construct the return string. If the string is empty, a string version of the input number is returned instead.

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):
    threes = '' if number % 3 else 'Pling'
    fives =  '' if number % 5 else 'Plang'
    sevens = '' if number % 7 else 'Plong'

    return f'{threes}{fives}{sevens}' or str(number)
Truthy and Falsey Values with f-strings

Use ternaries to build words and an f-string to assemble the result.

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 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().