Code:
from typing import List, Set, Dict, Tuple
from random import randrange

print("\n----------------------------")
print("-------  Exercice 3  -------")
print("----------------------------\n")

### 3 (a)
def sum_of_digits(n: int) -> int:
    sum = 0
    for digit in str(n):
        sum += int(digit)
    return sum


test_values = (0, 3, 29, 1111, 99999)
for test_value in test_values:
    print(sum_of_digits(test_value))


### 3 (b)
def sum_until_single_digit(n: int) -> int:
    while n > 9:
        n = sum_of_digits(n)
    return n


for test_value in test_values:
    print(sum_until_single_digit(test_value))

### 3 (c)
def is_multiple_of_3(n: int) -> bool:
    return sum_until_single_digit(n) in (0, 3, 6, 9)


for test_value in test_values:
    print(is_multiple_of_3(test_value))


### 3 (d)
num_samples = 1000
num_multiples = 0
for _ in range(num_samples):
    if is_multiple_of_3(randrange(0, 99)):
        num_multiples += 1

prob_3 = num_multiples / num_samples
print(f"Sample probability to get a multiple of 3: {prob_3}")


### 3 (e)
def one_digit_multiples_of(n: int) -> List[int]:
    multiples: List[int] = []
    i = 1
    while n * i < 10:
        multiples.append(n * i)
        i += 1
    return multiples


# other version
def one_digit_multiples_of2(n: int) -> List[int]:
    return list(range(n, 10, n))


### 3 (f)
multiples = [one_digit_multiples_of(n) for n in range(1, 10)]
print(multiples)

print("\n----------------------------")
print("-------  Exercice 4  -------")
print("----------------------------\n")


### 4 (a) & (b)
from dataclasses import dataclass

@dataclass
class SeriesSeason:
    series_name: str
    season_num: int
    start_year: int
    num_episodes: int


seasons: List[SeriesSeason] = [
    SeriesSeason("The Expanse", season_num=1, start_year=2015, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=4, start_year=2015, num_episodes=10),
    SeriesSeason("The Expanse", season_num=3, start_year=2018, num_episodes=13),
    SeriesSeason("Westworld", season_num=2, start_year=2014, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=1, start_year=2011, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=8, start_year=2015, num_episodes=6),
    SeriesSeason("The Expanse", season_num=2, start_year=2017, num_episodes=13),
    SeriesSeason("Game of Thrones", season_num=3, start_year=2013, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=6, start_year=2016, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=4, start_year=2014, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=7, start_year=2017, num_episodes=7),
    SeriesSeason("Westworld", season_num=1, start_year=2016, num_episodes=10),
    SeriesSeason("Game of Thrones", season_num=2, start_year=2012, num_episodes=10),
]

for season in seasons:
    print(season)


### 4 (c)
print("\n----------------------------\n")
num_episodes_by_series: Dict[str, int] = {}

for season in seasons:
    series = season.series_name
    if series in num_episodes_by_series:
        num_episodes_by_series[series] += season.num_episodes
    else:
        num_episodes_by_series[series] = season.num_episodes


print(f"{len(num_episodes_by_series)} series are defined:")
for name in num_episodes_by_series:
    print(f" - “{name}” has {num_episodes_by_series[name]} episodes")


### 4 (d)
print("\n----------------------------\n")
num_seasons = len(seasons)
for i in range(num_seasons):
    for j in range(i + 1, num_seasons):
        if (
            seasons[i].series_name == seasons[j].series_name
            and seasons[i].season_num == seasons[j].season_num
        ):
            print(
                f"There are multiple mentions of season {seasons[i].season_num} of “{seasons[i].series_name}”"
            )

# linear-complexity alternative
seen_seasons_by_series: Dict[str, Set[int]] = {}

for season in seasons:
    series = season.series_name
    if series not in seen_seasons_by_series:
        seen_seasons_by_series[series] = {season.season_num}
    else:
        if season.season_num in seen_seasons_by_series[series]:
            print(
                f"There are multiple mentions of season {season.season_num} of “{series}”"
            )
        seen_seasons_by_series[series].add(season.season_num)

### 4 (e)
for season in seasons:
    if season.season_num >= 2:
        for other_season in seasons:
            if (
                other_season.series_name == season.series_name
                and other_season.season_num < season.season_num
                and season.start_year < other_season.start_year
            ):
                print(
                    f"Season {season.season_num} of “{season.series_name}” "
                    f"reportedly started in {season.start_year}, which is earlier than "
                    f"season {other_season.season_num} ({other_season.start_year})"
                )
Last modified: Thursday, 22 October 2020, 23:37