Course: CSCI 1250

Lesson: Decision Structures

Description: GitHub

import os
from typing import Callable, Optional, TypeVar
 
INVALID_UNIT_ERR = ValueError("Invalid unit. Must be 'C', 'F', or 'K'.")
 
# python generics my beloved
T = TypeVar("T")
 
 
def prompt(
    msg: str,
    transform: Optional[Callable[[str], T]] = None,
    err_msg="Invalid response, please try again.",
) -> T:
    transform_fn = (lambda x: x) if transform is None else transform
 
    while True:
        try:
            return transform_fn(input(msg).strip())
        except Exception:
            # windows (command prompt only) doesn't have `clear` command
            os.system("cls" if os.name == "nt" else "clear")
            print(err_msg)
 
 
def parse_unit(unit: str) -> str:
    if unit.lower() not in ["c", "f", "k"]:
        raise INVALID_UNIT_ERR
 
    return unit.lower()
 
 
def calculate_temperatures(temperature: float, unit: str) -> dict[str, float]:
    conversions = {
        "c": temperature,
        "f": (temperature - 32) * 5 / 9,
        "k": temperature - 273.15,
    }
 
    celsius = conversions[unit]
 
    return {
        "celsius": celsius,
        "fahrenheit": celsius * 9 / 5 + 32,
        "kelvin": celsius + 273.15,
    }
 
 
def temperature_table(temperatures: dict[str, float]) -> str:
    headers = ["Unit", "Temperature"]
    rows = [(unit.title(), f"{temp:.2f}") for unit, temp in temperatures.items()]
 
    max_unit_w = max(len(headers[0]), max(len(row[0]) for row in rows))
    max_temp_w = max(len(headers[1]), max(len(row[1]) for row in rows))
 
    row_template = f"│ {{:<{max_unit_w}}} │ {{:>{max_temp_w}}} │"
    border = f"╭{'─' * (max_unit_w + 2)}┮{'─' * (max_temp_w + 2)}â•Ū"
    separator = f"├{'─' * (max_unit_w + 2)}┾{'─' * (max_temp_w + 2)}â”Ī"
    footer = f"╰{'─' * (max_unit_w + 2)}â”ī{'─' * (max_temp_w + 2)}â•Ŋ"
 
    table = [border, row_template.format(*headers), separator]
    table.extend(row_template.format(unit, temp) for unit, temp in rows)
    table.append(footer)
 
    return "\n".join(table)
 
 
def main() -> None:
    temperature = prompt(
        "Enter a temperature: ", float, "Please enter a valid number."
    )
 
    unit = prompt(
        "Enter the unit of the temperature (C/F/K): ",
        parse_unit,
        "Please enter 'C', 'F', or 'K'.",
    )
 
    temperatures = calculate_temperatures(temperature, unit)
 
    print(f"Based on {temperature:.2f}°{unit.upper()}:")
    print(temperature_table(temperatures))
 
 
if __name__ == "__main__":
    main()