Queer European MD passionate about IT
Просмотр исходного кода

Preparing git repo for final project

Davte 1 год назад
Родитель
Сommit
63d06d6b35
67 измененных файлов с 1587 добавлено и 0 удалено
  1. 8 0
      problems/prepare.sh
  2. 16 0
      problems/pset0/einstein/einstein.py
  3. 9 0
      problems/pset0/faces/faces.py
  4. 7 0
      problems/pset0/indoor/indoor.py
  5. 7 0
      problems/pset0/playback/playback.py
  6. 17 0
      problems/pset0/tip/tip.py
  7. 12 0
      problems/pset1/bank/bank.py
  8. 28 0
      problems/pset1/deep/deep.py
  9. 22 0
      problems/pset1/extensions/extensions.py
  10. 17 0
      problems/pset1/interpreter/interpreter.py
  11. 17 0
      problems/pset1/meal/meal.py
  12. 16 0
      problems/pset2/camel/camel.py
  13. 15 0
      problems/pset2/coke/coke.py
  14. 14 0
      problems/pset2/nutrition/nutrition.py
  15. 39 0
      problems/pset2/plates/plates.py
  16. 11 0
      problems/pset2/twttr/twttr.py
  17. 21 0
      problems/pset3/fuel/fuel.py
  18. 16 0
      problems/pset3/grocery/grocery.py
  19. 41 0
      problems/pset3/outdated/outdated.py
  20. 27 0
      problems/pset3/taqueria/taqueria.py
  21. 25 0
      problems/pset4/adieu/adieu.py
  22. 24 0
      problems/pset4/bitcoin/bitcoin.py
  23. 10 0
      problems/pset4/emojize/emojize.py
  24. 24 0
      problems/pset4/figlet/figlet.py
  25. 28 0
      problems/pset4/game/game.py
  26. 43 0
      problems/pset4/professor/professor.py
  27. 17 0
      problems/pset5/test_bank/bank.py
  28. 77 0
      problems/pset5/test_bank/test_bank.py
  29. 35 0
      problems/pset5/test_fuel/fuel.py
  30. 41 0
      problems/pset5/test_fuel/test_fuel.py
  31. 39 0
      problems/pset5/test_plates/plates.py
  32. 39 0
      problems/pset5/test_plates/test_plates.py
  33. 33 0
      problems/pset5/test_twttr/test_twttr.py
  34. 16 0
      problems/pset5/test_twttr/twttr.py
  35. 33 0
      problems/pset6/lines/lines.py
  36. 13 0
      problems/pset6/lines/test_lines.py
  37. 39 0
      problems/pset6/pizza/pizza.py
  38. 6 0
      problems/pset6/pizza/regular.csv
  39. 54 0
      problems/pset6/scourgify/after.csv
  40. 54 0
      problems/pset6/scourgify/before.csv
  41. 7 0
      problems/pset6/scourgify/in.csv
  42. 5 0
      problems/pset6/scourgify/out.csv
  43. 42 0
      problems/pset6/scourgify/scourgify.py
  44. BIN
      problems/pset6/shirt/after1.jpg
  45. BIN
      problems/pset6/shirt/after2.jpg
  46. BIN
      problems/pset6/shirt/after3.jpg
  47. BIN
      problems/pset6/shirt/before1.jpg
  48. BIN
      problems/pset6/shirt/before2.jpg
  49. BIN
      problems/pset6/shirt/before3.jpg
  50. BIN
      problems/pset6/shirt/shirt.png
  51. 43 0
      problems/pset6/shirt/shirt.py
  52. 19 0
      problems/pset7/numb3rs/numb3rs.py
  53. 32 0
      problems/pset7/numb3rs/test_numb3rs.py
  54. 23 0
      problems/pset7/response/response.py
  55. 16 0
      problems/pset7/response/test_response.py
  56. 37 0
      problems/pset7/um/test_um.py
  57. 15 0
      problems/pset7/um/um.py
  58. 20 0
      problems/pset7/watch/watch.py
  59. 102 0
      problems/pset7/working/test_working.py
  60. 39 0
      problems/pset7/working/working.py
  61. 26 0
      problems/pset8/jar/jar.py
  62. 46 0
      problems/pset8/jar/test_jar.py
  63. 39 0
      problems/pset8/seasons/seasons.py
  64. 44 0
      problems/pset8/seasons/test_seasons.py
  65. BIN
      problems/pset8/shirtificate/shirtificate.pdf
  66. BIN
      problems/pset8/shirtificate/shirtificate.png
  67. 22 0
      problems/pset8/shirtificate/shirtificate.py

+ 8 - 0
problems/prepare.sh

@@ -0,0 +1,8 @@
+#!/usr/bin/bash
+this_script_directory=$(cd "$(dirname "${0}")" && pwd);
+read -r -p "Enter the name of the next challenge      " challengeName;
+cd "$this_script_directory";
+mkdir "$challengeName";
+cd "$challengeName";
+code "$challengeName".py
+code "test_$challengeName".py

+ 16 - 0
problems/pset0/einstein/einstein.py

@@ -0,0 +1,16 @@
+c = 300000000
+
+
+def compute_energy():
+    mass = None
+    while not isinstance(mass, int):
+        input_string = input("Enter a mass:\t\t")
+        try:
+            mass = int(input_string)
+        except (ValueError, TypeError):
+            mass = None
+    print(mass*(c**2))
+
+
+if __name__ == "__main__":
+    compute_energy()

+ 9 - 0
problems/pset0/faces/faces.py

@@ -0,0 +1,9 @@
+def replace_emoticons_with_emojis():
+    input_string = input("Say something quickly and I will repeat it slowly!\t\t")
+    for emoticon, emoji in {':)': '🙂', ':(': '🙁'}.items():
+        input_string = input_string.replace(emoticon, emoji)
+    print(input_string)
+
+
+if __name__ == "__main__":
+    replace_emoticons_with_emojis()

+ 7 - 0
problems/pset0/indoor/indoor.py

@@ -0,0 +1,7 @@
+def prompt_user_and_echo_lowercase():
+    input_string = input("Shout me something and I will repeat it with indoor voice!\t\t")
+    print(input_string.lower())
+
+
+if __name__ == "__main__":
+    prompt_user_and_echo_lowercase()

+ 7 - 0
problems/pset0/playback/playback.py

@@ -0,0 +1,7 @@
+def prompt_user_and_echo_lowercase():
+    input_string = input("Say something quickly and I will repeat it slowly!\t\t")
+    print(input_string.replace(' ', '...'))
+
+
+if __name__ == "__main__":
+    prompt_user_and_echo_lowercase()

+ 17 - 0
problems/pset0/tip/tip.py

@@ -0,0 +1,17 @@
+def main():
+    dollars = dollars_to_float(input("How much was the meal? "))
+    percent = percent_to_float(input("What percentage would you like to tip? "))
+    tip = dollars * percent
+    print(f"Leave ${tip:.2f}")
+
+
+def dollars_to_float(d):
+    return float(d[1:])
+
+
+def percent_to_float(p):
+    return float(p[:-1])/100
+
+
+if __name__ == "__main__":
+    main()

+ 12 - 0
problems/pset1/bank/bank.py

@@ -0,0 +1,12 @@
+def main():
+    greeting = input("Greeting:\t\t").lower().strip()
+    if greeting.startswith("hello"):
+        print("$0")
+    elif greeting.startswith("h"):
+        print("$20")
+    else:
+        print("$100")
+
+
+if __name__ == "__main__":
+    main()

+ 28 - 0
problems/pset1/deep/deep.py

@@ -0,0 +1,28 @@
+import re
+answer_regex = re.compile(r"\s*(42|forty[ -]?two)\s*", re.IGNORECASE)
+
+def ifelse_answer():
+    answer = input("What is the Answer to the Great Question of Life, the Universe, and Everything?\t\t")
+    answer = answer.strip()
+    answer = answer.replace('-', '')
+    answer = answer.replace(' ', '')
+    answer = answer.lower()
+    if answer in ('42', 'fortytwo'):
+        print("Yes")
+    else:
+        print("No")
+
+
+def regex_answer():
+    print('Yes'
+          if answer_regex.match(
+                input("What is the Answer to the Great Question of Life, the Universe, and Everything?\t\t"))
+          else 'No')
+
+
+def oneliner():
+    print({True: 'Yes', False: 'No'}[input("What is the Answer to the Great Question of Life, the Universe, and Everything?\t\t").lower().strip() in ('42', 'fortytwo', 'forty two', 'forty-two')])
+
+
+if __name__ == "__main__":
+    regex_answer()

+ 22 - 0
problems/pset1/extensions/extensions.py

@@ -0,0 +1,22 @@
+extensions = {
+    ".gif": "image/gif",
+    ".jpg": "image/jpeg",
+    ".jpeg": "image/jpeg",
+    ".png": "image/png",
+    ".pdf": "application/pdf",
+    ".txt": "text/plain",
+    ".zip": "application/zip"
+}
+
+def main():
+    file_name = input("Enter file name:\t\t").lower().strip()
+    for extension, mime in extensions.items():
+        if file_name.endswith(extension):
+            print(mime)
+            break
+    else:
+        print("application/octet-stream")
+
+
+if __name__ == "__main__":
+    main()

+ 17 - 0
problems/pset1/interpreter/interpreter.py

@@ -0,0 +1,17 @@
+def main():
+    expression = input("Enter an arithmetic expression:\t\t")
+    x, y, z = expression.split()
+    result = 0.0
+    if y == '+':
+        result = int(x) + int(z)
+    elif y == '-':
+        result = int(x) - int(z)
+    elif y == '*':
+        result = int(x) * int(z)
+    elif y == '/':
+        result = int(x) / int(z)
+    print(round(float(result), 1))
+
+
+if __name__ == "__main__":
+    main()

+ 17 - 0
problems/pset1/meal/meal.py

@@ -0,0 +1,17 @@
+def main():
+    t = convert(input("What time is it?\t\t"))
+    if 7 <= t <= 8:
+        print("breakfast time")
+    elif 12 <= t <= 13:
+        print("lunch time")
+    elif 18 <= t <= 19:
+        print("dinner time")
+
+
+def convert(time):
+    h ,m = map(int, time.split(":"))
+    return float(h + m / 60)
+
+
+if __name__ == "__main__":
+    main()

+ 16 - 0
problems/pset2/camel/camel.py

@@ -0,0 +1,16 @@
+def main():
+    camel_string = input("camelCase:\t\t")
+    print(f"snake_case:\t\t{convert_to_snake_case(camel_string)}")
+
+
+def convert_to_snake_case(s: str):
+    result = ''
+    for char in s:
+        if char == char.upper():
+            result += '_'
+        result += char.lower()
+    return result
+
+
+if __name__ == "__main__":
+    main()

+ 15 - 0
problems/pset2/coke/coke.py

@@ -0,0 +1,15 @@
+def main():
+    total_coins = 0
+    while total_coins < 50:
+        print(f"Amount Due: {50 - total_coins}")
+        new_coin = input("Insert coin:\t\t")
+        if new_coin not in ('5', '10', '25'):
+            new_coin = 0
+        else:
+            new_coin = int(new_coin)
+        total_coins += new_coin
+    print(f"Change Owed: {total_coins - 50}")
+
+
+if __name__ == "__main__":
+    main()

+ 14 - 0
problems/pset2/nutrition/nutrition.py

@@ -0,0 +1,14 @@
+fruits = {'apple': '130', 'avocado': '50', 'banana': '110', 'cantaloupe': '50',
+ 'grapefruit': '60', 'grapes': '90', 'honeydew melon': '50', 'kiwifruit': '90', 'lemon': '15', 'lime': '20',
+  'nectarine': '60', 'orange': '80', 'peach': '60', 'pear': '100', 'pineapple': '50', 'plums': '70', 'strawberries': '50',
+  'sweet cherries': '100', 'tangerine': '50', 'watermelon': '80'}
+
+
+def main():
+    fruit = input("Item:\t\t").lower()
+    if fruit in fruits:
+        print(f"Calories:\t\t{fruits[fruit]}")
+
+
+if __name__ == "__main__":
+    main()

+ 39 - 0
problems/pset2/plates/plates.py

@@ -0,0 +1,39 @@
+import re
+
+
+def main():
+    plate = input("Plate: ")
+    if is_valid(plate):
+        print("Valid")
+    else:
+        print("Invalid")
+
+
+def is_valid(s):
+    if len(s) < 2 or len(s) > 6:
+        return False
+    return re.match(r"^[A-Z]{2,}([1-9]\d+)?(?![A-Z])$", s)
+
+
+def loopy_is_valid(s):
+    length = 0
+    has_digits = False
+    for n, c in enumerate(s):
+        if n > 6:
+            return False
+        if not c.isalnum():
+            return False
+        if n < 2 and not c.isalpha():
+            return False
+        if c.isdigit():
+            if not has_digits and c == '0':
+                return False
+            has_digits = True
+        elif has_digits:
+            return False
+        length += 1
+    return length >= 2
+
+
+if __name__ == "__main__":
+    main()

+ 11 - 0
problems/pset2/twttr/twttr.py

@@ -0,0 +1,11 @@
+def main():
+    tweet = input("Input: \t\t")
+    result = ''
+    for c in tweet:
+        if c.lower() not in ('a', 'e', 'i', 'o', 'u'):
+            result += c
+    print(f"Output: {result}")
+
+
+if __name__ == "__main__":
+    main()

+ 21 - 0
problems/pset3/fuel/fuel.py

@@ -0,0 +1,21 @@
+def main():
+    while True:
+        fuel = input("Fraction:\t\t")
+        try:
+            x, y = map(int, fuel.split('/'))
+            if x > y:
+                continue
+            result = x / y
+            break
+        except (ValueError, ZeroDivisionError):
+            continue
+    if result <= 0.01:
+        print("E")
+    elif result >= 0.99:
+        print("F")
+    else:
+        print(f"{int(round(result*100, 0))}%")
+
+
+if __name__ == "__main__":
+    main()

+ 16 - 0
problems/pset3/grocery/grocery.py

@@ -0,0 +1,16 @@
+def main():
+    shopping_list = {}
+    while True:
+        try:
+            item = input("").upper()
+        except EOFError:
+            break
+        if item not in shopping_list:
+            shopping_list[item] = 0
+        shopping_list[item] += 1
+    for item, quantity in sorted(shopping_list.items(), key=lambda x: x[0]):
+        print(f"{quantity} {item}")
+
+
+if __name__ == "__main__":
+    main()

+ 41 - 0
problems/pset3/outdated/outdated.py

@@ -0,0 +1,41 @@
+import re
+
+months = [
+    "January",
+    "February",
+    "March",
+    "April",
+    "May",
+    "June",
+    "July",
+    "August",
+    "September",
+    "October",
+    "November",
+    "December"
+]
+
+
+def main():
+    while True:
+        try:
+            entered_date = input("Date: ").strip()
+        except Exception:
+            continue
+        if re.match(r"\d+/\d+/\d+", entered_date):
+            month, day, year = map(int, entered_date.split('/'))
+        elif re.match(r"\w+ \d{1,2}, \d{4}", entered_date):
+            entered_date = entered_date.replace(",", "")
+            month, day, year = entered_date.split(' ')
+            month = months.index(month) + 1
+            day, year = map(int, (day, year))
+        else:
+            continue
+        if month > 12 or day > 31:
+            continue
+        break
+    print(f"{year:04d}-{month:02d}-{day:02d}")
+
+
+if __name__ == "__main__":
+    main()

+ 27 - 0
problems/pset3/taqueria/taqueria.py

@@ -0,0 +1,27 @@
+menu = {
+    "Baja Taco": 4.00,
+    "Burrito": 7.50,
+    "Bowl": 8.50,
+    "Nachos": 11.00,
+    "Quesadilla": 8.50,
+    "Super Burrito": 8.50,
+    "Super Quesadilla": 9.50,
+    "Taco": 3.00,
+    "Tortilla Salad": 8.00
+}
+
+
+def main():
+    total = 0.0
+    while True:
+        try:
+            dish = input("Item: :\t\t").title()
+        except EOFError:
+            break
+        if dish in menu:
+            total += menu[dish]
+            print(f"Total: ${total:.2f}")
+
+
+if __name__ == "__main__":
+    main()

+ 25 - 0
problems/pset4/adieu/adieu.py

@@ -0,0 +1,25 @@
+def main():
+    names = []
+    while True:
+        try:
+            name = input("Name: ")
+            names.append(name)
+        except EOFError:
+            break
+    if len(names) == 0:
+        return
+    result = "Adieu, adieu, to "
+    for n, name in enumerate(names):
+        if n == 0:
+            result += name
+        elif n == 1 and len(names) == 2:
+            result += f" and {name}"
+        elif n == len(names) - 1:
+            result += f", and {name}"
+        else:
+            result += f", {name}"
+    print(result)
+
+
+if __name__ == "__main__":
+    main()

+ 24 - 0
problems/pset4/bitcoin/bitcoin.py

@@ -0,0 +1,24 @@
+import json
+import requests
+import sys
+
+
+def main():
+    try:
+        n = float(sys.argv[1])
+    except IndexError:
+        sys.exit("Missing command-line argument")
+    except (ValueError, TypeError):
+        sys.exit("Command-line argument is not a number")
+    try:
+        response = requests.get("https://api.coindesk.com/v1/bpi/currentprice.json")
+        data = response.json()
+        conversion_rate = float(data['bpi']['USD']['rate'].replace(',', ''))
+    except requests.RequestException:
+        pass
+    print(f"${n * conversion_rate:,.4f}")
+
+
+
+if __name__ == "__main__":
+    main()

+ 10 - 0
problems/pset4/emojize/emojize.py

@@ -0,0 +1,10 @@
+import emoji
+
+
+def main():
+    input_text = input("Input: ").strip()
+    print(emoji.emojize(emoji.emojize(input_text, language='alias'), language='en'))
+
+
+if __name__ == "__main__":
+    main()

+ 24 - 0
problems/pset4/figlet/figlet.py

@@ -0,0 +1,24 @@
+import sys
+
+from pyfiglet import Figlet
+
+figlet = Figlet()
+
+
+def main():
+    if len(sys.argv) > 1:
+        if len(sys.argv) != 3:
+            sys.exit("This script takes 2 or no command-line arguments")
+        if sys.argv[1] not in ('-f', '--font'):
+            sys.exit(f"Unknown option `{sys.argv[1]}`")
+        available_fonts = figlet.getFonts()
+        font = sys.argv[2]
+        if font not in available_fonts:
+            sys.exit(f"Unknown font `{sys.argv[2]}`")
+        figlet.setFont(font=font)
+    input_text = input("Input: ")
+    print(figlet.renderText(input_text))
+
+
+if __name__ == "__main__":
+    main()

+ 28 - 0
problems/pset4/game/game.py

@@ -0,0 +1,28 @@
+import random
+
+
+def main():
+    n = -1
+    while n < 1:
+        try:
+            n = int(input("Level: "))
+        except (TypeError, ValueError):
+            continue
+    random.seed()
+    n_to_guess = random.randint(1, n)
+    guess = -1
+    while n_to_guess - guess != 0:
+        try:
+            guess = int(input("Guess: "))
+        except (TypeError, ValueError):
+            continue
+        if guess == n_to_guess:
+            print("Just right!")
+        if guess < n_to_guess:
+            print("Too small!")
+        if guess > n_to_guess:
+            print("Too large!")
+
+
+if __name__ == "__main__":
+    main()

+ 43 - 0
problems/pset4/professor/professor.py

@@ -0,0 +1,43 @@
+import random
+
+
+def main():
+    # random.seed()  # This breaks check50 😠
+    score = 0
+    level = get_level()
+    for _ in range(10):
+        x = generate_integer(level)
+        y = generate_integer(level)
+        for _ in range(3):
+            try:
+                answer = int(input(f"{x} + {y} ="))
+            except (ValueError, TypeError):
+                answer = -1
+            if answer == x + y:
+                score += 1
+                break
+            else:
+                print("EEE")
+        else:
+            print(f"{x} + {y} = {x + y}")
+    print(f"Score: {score}")
+
+
+def get_level():
+    level = 0
+    while level not in (1, 2, 3):
+        try:
+            level = int(input("Level: "))
+        except (ValueError, TypeError):
+            level = 0
+    return level
+
+
+def generate_integer(level):
+    if level not in (1, 2, 3):
+        raise ValueError
+    return random.randint((10**(level - 1) if level > 1 else 0), 10**level - 1)
+
+
+if __name__ == "__main__":
+    main()

+ 17 - 0
problems/pset5/test_bank/bank.py

@@ -0,0 +1,17 @@
+def main():
+    greeting = input("Greeting:\t\t")
+    print(value(f"${greeting}"))
+
+
+def value(greeting: str) -> str:
+    greeting = greeting.lower().strip()
+    if greeting.startswith("hello"):
+        return 0
+    elif greeting.startswith("h"):
+        return 20
+    else:
+        return 100
+
+
+if __name__ == "__main__":
+    main()

+ 77 - 0
problems/pset5/test_bank/test_bank.py

@@ -0,0 +1,77 @@
+from bank import value
+
+
+def main():
+    test_lower()
+    test_upper()
+    test_title_case()
+    test_mixed_case()
+    test_zero()
+    test_twenty()
+    test_empty()
+    test_hundred()
+    test_containing_hello()
+
+
+def test_lower():
+    assert value('hello') == 0
+    assert value('hey') == 20
+    assert value('banana') == 100
+
+
+def test_upper():
+    assert value('HELLO') == 0
+    assert value('HEY') == 20
+    assert value('BANANA') == 100
+
+
+def test_title_case():
+    assert value('Hello') == 0
+    assert value('Hey') == 20
+    assert value('Banana') == 100
+
+
+def test_mixed_case():
+    assert value('Hello') == 0
+    assert value('Hey') == 20
+    assert value('Banana') == 100
+
+
+def test_zero():
+    assert value('Hello') == 0
+    assert value('hello') == 0
+    assert value('HELLO') == 0
+    assert value('HeLlO') == 0
+    assert value('hElLo') == 0
+
+
+def test_twenty():
+    assert value('Hey') == 20
+    assert value('hey') == 20
+    assert value('HEY') == 20
+    assert value('HeY') == 20
+    assert value('hEy') == 20
+    assert value('h') == 20
+    assert value('h20') == 20
+
+
+def test_empty():
+    assert value('') == 100
+
+
+def test_hundred():
+    assert value('Banana') == 100
+    assert value('banana') == 100
+    assert value('BANANA') == 100
+    assert value('12345678') == 100
+    assert value('nothello') == 100
+
+def test_containing_hello():
+    assert value('aHello') == 100
+    assert value('ahello') == 100
+    assert value('aHELLO') == 100
+    assert value('aHeLlO') == 100
+    assert value('ahElLo') == 100
+
+if __name__ == "__main__":
+    main()

+ 35 - 0
problems/pset5/test_fuel/fuel.py

@@ -0,0 +1,35 @@
+def main():
+    while True:
+        fuel = input("Fraction:\t\t")
+        try:
+            result = convert(fuel)
+            break
+        except (ValueError, ZeroDivisionError):
+            continue
+    print(gauge(result))
+
+
+
+def convert(fraction: str) -> int:
+    try:
+        x, y = map(int, fraction.split('/'))
+    except (ValueError, TypeError):
+        raise ValueError
+    if x > y:
+        raise ValueError
+    if y == 0:
+        raise ZeroDivisionError
+    return int(round(x / y*100, 0))
+
+
+def gauge(percentage):
+    if percentage <= 1:
+        return "E"
+    elif percentage >= 99:
+        return "F"
+    else:
+        return f"{percentage}%"
+
+
+if __name__ == "__main__":
+    main()

+ 41 - 0
problems/pset5/test_fuel/test_fuel.py

@@ -0,0 +1,41 @@
+from fuel import convert, gauge
+
+def main():
+    test_convert()
+    test_gauge()
+    test_value_error()
+    test_zero_division_error()
+
+def test_convert():
+    assert convert("0/4") == 0
+    assert convert("1/4") == 25
+    assert convert("4/4") == 100
+
+def test_value_error():
+    try:
+        convert("5/4")
+    except ValueError:
+        pass
+    else:
+        raise Exception("ValueError not raised with y>x")
+
+
+def test_zero_division_error():
+    try:
+        convert("5/0")
+    except ZeroDivisionError:
+        pass
+    else:
+        raise Exception("ZeroDivisionError not raised with y = 0")
+
+
+def test_gauge():
+    assert gauge(100) == 'F'
+    assert gauge(99) == 'F'
+    assert gauge(0) == 'E'
+    assert gauge(1) == 'E'
+    assert gauge(27) == '27%'
+
+
+if __name__ == '__main__':
+    main()

+ 39 - 0
problems/pset5/test_plates/plates.py

@@ -0,0 +1,39 @@
+import re
+
+
+def main():
+    plate = input("Plate: ")
+    if is_valid(plate):
+        print("Valid")
+    else:
+        print("Invalid")
+
+
+def is_valid(s):
+    if len(s) < 2 or len(s) > 6:
+        return False
+    return re.match(r"^[A-Z]{2,}([1-9]\d+)?(?![A-Z])$", s) is not None
+
+
+def loopy_is_valid(s):
+    length = 0
+    has_digits = False
+    for n, c in enumerate(s):
+        if n > 6:
+            return False
+        if not c.isalnum():
+            return False
+        if n < 2 and not c.isalpha():
+            return False
+        if c.isdigit():
+            if not has_digits and c == '0':
+                return False
+            has_digits = True
+        elif has_digits:
+            return False
+        length += 1
+    return length >= 2
+
+
+if __name__ == "__main__":
+    main()

+ 39 - 0
problems/pset5/test_plates/test_plates.py

@@ -0,0 +1,39 @@
+from plates import is_valid
+
+
+def main():
+    test_incipit()
+    test_length()
+    test_digits()
+    test_forbidden_characters()
+
+
+def test_incipit():
+    assert is_valid('11AA11') == False
+    assert is_valid('11AA') == False
+    assert is_valid('1AA2') == False
+    assert is_valid('2AAP') == False
+    assert is_valid('A1111') == False
+    assert is_valid('AA1111') == True
+
+
+def test_length():
+    assert is_valid('') == False
+    assert is_valid('A') == False
+    assert is_valid('AAAAAA111111') == False
+
+
+def test_digits():
+    assert is_valid('AA111A') == False
+    assert is_valid('AA0111') == False
+
+
+def test_forbidden_characters():
+    assert is_valid('AA1.1') == False
+    assert is_valid('AA1,1') == False
+    assert is_valid('AA1 1') == False
+    assert is_valid('AA11!') == False
+
+
+if __name__ == "__main__":
+    main()

+ 33 - 0
problems/pset5/test_twttr/test_twttr.py

@@ -0,0 +1,33 @@
+from twttr import shorten
+
+
+def main():
+    test_uppercase()
+    test_lowercase()
+    test_null()
+    test_digits()
+    test_punctuation()
+
+
+def test_uppercase():
+    assert shorten('TWITTER') == 'TWTTR'
+
+
+def test_lowercase():
+    assert shorten('twitter') == 'twttr'
+
+
+def test_null():
+    assert shorten('') == ''
+
+
+def test_digits():
+    assert shorten('abc123') == 'bc123'
+
+
+def test_punctuation():
+    assert shorten('abc,') == 'bc,'
+
+
+if __name__ == "__main__":
+    main()

+ 16 - 0
problems/pset5/test_twttr/twttr.py

@@ -0,0 +1,16 @@
+def main():
+    tweet = input("Input: \t\t")
+    result = shorten(tweet)
+    print(f"Output: {result}")
+
+
+def shorten(word: str) -> str:
+    result = ''
+    for c in word:
+        if c.lower() not in ('a', 'e', 'i', 'o', 'u'):
+            result += c
+    return result
+
+
+if __name__ == "__main__":
+    main()

+ 33 - 0
problems/pset6/lines/lines.py

@@ -0,0 +1,33 @@
+import os
+import sys
+
+
+def main():
+    try:
+        file_name = sys.argv[1]
+    except IndexError:
+        print("Provide a file name from command line!")
+        sys.exit(1)
+    if len(sys.argv) > 2:
+        print("Too many command-line arguments")
+        sys.exit(1)
+    if not os.path.isfile(file_name):  # Or try opening and catch FileNotFoundError
+        print(f"The file `{file_name}` does not exist!")
+        sys.exit(1)
+    if not file_name.endswith('.py'):
+        print(f"The file `{file_name}` is not a python script (does not end with the proper suffix)!")
+        sys.exit(1)
+    line_count = count_lines(file_name)
+    print(line_count)
+
+
+def count_lines(file_name: str) -> int:
+    line_count = 0
+    with open(file_name, 'r') as file_object:
+        for line in file_object:
+            if len(line.strip()) > 0 and not line.strip().startswith('#'):
+                line_count += 1
+    return line_count
+
+if __name__ == "__main__":
+    main()

+ 13 - 0
problems/pset6/lines/test_lines.py

@@ -0,0 +1,13 @@
+from lines import count_lines
+
+
+def main():
+    test_count_lines()
+
+
+def test_count_lines():
+    assert count_lines('lines.py') == 28
+
+
+if __name__ == "__main__":
+    main()

+ 39 - 0
problems/pset6/pizza/pizza.py

@@ -0,0 +1,39 @@
+import csv
+import os
+import sys
+
+import tabulate
+
+
+def main():
+    try:
+        file_name = sys.argv[1]
+    except IndexError:
+        print("Too few command-line arguments")
+        sys.exit(1)
+    if len(sys.argv) > 2:
+        print("Too many command-line arguments")
+        sys.exit(1)
+    if not os.path.isfile(file_name):  # Or try opening and catch FileNotFoundError
+        print(f"File does not exist")
+        sys.exit(1)
+    if not file_name.endswith('.csv'):
+        print(f"Not a CSV file")
+        sys.exit(1)
+    grid = get_grid_from_csv(file_name)
+    print(grid)
+
+
+def get_grid_from_csv(file_name: str) -> str:
+    headers = None
+    table = []
+    with open(file_name, 'r') as file_object:
+        reader = csv.DictReader(file_object)
+        for row in reader:
+            if headers is None:
+                headers = list(row.keys())
+            table.append(tuple(row[field] for field in row))
+    return tabulate.tabulate(table, headers, tablefmt="grid")
+
+if __name__ == "__main__":
+    main()

+ 6 - 0
problems/pset6/pizza/regular.csv

@@ -0,0 +1,6 @@
+Regular Pizza,Small,Large
+Cheese,$13.50,$18.95
+1 topping,$14.75,$20.95
+2 toppings,$15.95,$22.95
+3 toppings,$16.95,$24.95
+Special,$18.50,$26.95

+ 54 - 0
problems/pset6/scourgify/after.csv

@@ -0,0 +1,54 @@
+first,last,house
+Hannah,Abbott,Hufflepuff
+Katie,Bell,Gryffindor
+Susan,Bones,Hufflepuff
+Terry,Boot,Ravenclaw
+Lavender,Brown,Gryffindor
+Millicent,Bulstrode,Slytherin
+Cho,Chang,Ravenclaw
+Penelope,Clearwater,Ravenclaw
+Vincent,Crabbe,Slytherin
+Colin,Creevey,Gryffindor
+Dennis,Creevey,Gryffindor
+Cedric,Diggory,Hufflepuff
+Marietta,Edgecombe,Ravenclaw
+Justin,Finch-Fletchley,Hufflepuff
+Seamus,Finnigan,Gryffindor
+Anthony,Goldstein,Ravenclaw
+Gregory,Goyle,Slytherin
+Hermione,Granger,Gryffindor
+Angelina,Johnson,Gryffindor
+Lee,Jordan,Gryffindor
+Neville,Longbottom,Gryffindor
+Luna,Lovegood,Ravenclaw
+Remus,Lupin,Gryffindor
+Draco,Malfoy,Slytherin
+Scorpius,Malfoy,Slytherin
+Ernie,Macmillan,Hufflepuff
+Minerva,McGonagall,Gryffindor
+Eloise,Midgen,Gryffindor
+Cormac,McLaggen,Gryffindor
+Graham,Montague,Slytherin
+Theodore,Nott,Slytherin
+Pansy,Parkinson,Slytherin
+Padma,Patil,Gryffindor
+Parvati,Patil,Gryffindor
+Harry,Potter,Gryffindor
+Tom,Riddle,Slytherin
+Demelza,Robins,Gryffindor
+Newt,Scamander,Hufflepuff
+Horace,Slughorn,Slytherin
+Zacharias,Smith,Hufflepuff
+Severus,Snape,Slytherin
+Alicia,Spinnet,Gryffindor
+Pomona,Sprout,Hufflepuff
+Dean,Thomas,Gryffindor
+Romilda,Vane,Gryffindor
+Myrtle,Warren,Ravenclaw
+Fred,Weasley,Gryffindor
+George,Weasley,Gryffindor
+Ginny,Weasley,Gryffindor
+Percy,Weasley,Gryffindor
+Ron,Weasley,Gryffindor
+Oliver,Wood,Gryffindor
+Blaise,Zabini,Slytherin

+ 54 - 0
problems/pset6/scourgify/before.csv

@@ -0,0 +1,54 @@
+name,house
+"Abbott, Hannah",Hufflepuff
+"Bell, Katie",Gryffindor
+"Bones, Susan",Hufflepuff
+"Boot, Terry",Ravenclaw
+"Brown, Lavender",Gryffindor
+"Bulstrode, Millicent",Slytherin
+"Chang, Cho",Ravenclaw
+"Clearwater, Penelope",Ravenclaw
+"Crabbe, Vincent",Slytherin
+"Creevey, Colin",Gryffindor
+"Creevey, Dennis",Gryffindor
+"Diggory, Cedric",Hufflepuff
+"Edgecombe, Marietta",Ravenclaw
+"Finch-Fletchley, Justin",Hufflepuff
+"Finnigan, Seamus",Gryffindor
+"Goldstein, Anthony",Ravenclaw
+"Goyle, Gregory",Slytherin
+"Granger, Hermione",Gryffindor
+"Johnson, Angelina",Gryffindor
+"Jordan, Lee",Gryffindor
+"Longbottom, Neville",Gryffindor
+"Lovegood, Luna",Ravenclaw
+"Lupin, Remus",Gryffindor
+"Malfoy, Draco",Slytherin
+"Malfoy, Scorpius",Slytherin
+"Macmillan, Ernie",Hufflepuff
+"McGonagall, Minerva",Gryffindor
+"Midgen, Eloise",Gryffindor
+"McLaggen, Cormac",Gryffindor
+"Montague, Graham",Slytherin
+"Nott, Theodore",Slytherin
+"Parkinson, Pansy",Slytherin
+"Patil, Padma",Gryffindor
+"Patil, Parvati",Gryffindor
+"Potter, Harry",Gryffindor
+"Riddle, Tom",Slytherin
+"Robins, Demelza",Gryffindor
+"Scamander, Newt",Hufflepuff
+"Slughorn, Horace",Slytherin
+"Smith, Zacharias",Hufflepuff
+"Snape, Severus",Slytherin
+"Spinnet, Alicia",Gryffindor
+"Sprout, Pomona",Hufflepuff
+"Thomas, Dean",Gryffindor
+"Vane, Romilda",Gryffindor
+"Warren, Myrtle",Ravenclaw
+"Weasley, Fred",Gryffindor
+"Weasley, George",Gryffindor
+"Weasley, Ginny",Gryffindor
+"Weasley, Percy",Gryffindor
+"Weasley, Ron",Gryffindor
+"Wood, Oliver",Gryffindor
+"Zabini, Blaise",Slytherin

+ 7 - 0
problems/pset6/scourgify/in.csv

@@ -0,0 +1,7 @@
+name,house
+"Abbott, Hannah",Hufflepuff
+"Bell, Katie",Gryffindor
+"Bones, Susan",Hufflepuff
+"Boot, Terry",Ravenclaw
+"Brown, Lavender",Gryffindor
+"Bulstrode, Millicent",Slytherin

+ 5 - 0
problems/pset6/scourgify/out.csv

@@ -0,0 +1,5 @@
+first,last,house
+Susan,Bones,Hufflepuff
+Terry,Boot,Ravenclaw
+Lavender,Brown,Gryffindor
+Millicent,Bulstrode,Slytherin

+ 42 - 0
problems/pset6/scourgify/scourgify.py

@@ -0,0 +1,42 @@
+import csv
+import os
+import sys
+
+
+def main():
+    try:
+        input_file_name, output_file_name = sys.argv[1:3]
+    except IndexError:
+        print("Too few command-line arguments")
+        sys.exit(1)
+    if len(sys.argv) > 3:
+        print("Too many command-line arguments")
+        sys.exit(1)
+    if not os.path.isfile(input_file_name):  # Or try opening and catch FileNotFoundError
+        print(f"Could not read {input_file_name}")
+        sys.exit(1)
+    if not input_file_name.endswith('.csv'):
+        print(f"Not a CSV file")
+        sys.exit(1)
+    write_names_to_csv_file(read_names_from_csv_file(input_file_name), output_file_name)
+
+
+def read_names_from_csv_file(input_file_name):
+    with open(input_file_name, 'r') as input_file:
+        reader = csv.DictReader(input_file)
+        for row in reader:
+            yield row
+
+
+def write_names_to_csv_file(input_data, output_file_name):
+    with open(output_file_name, 'w') as output_file:
+        writer = csv.DictWriter(output_file, fieldnames=['first', 'last', 'house'])
+        writer.writeheader()
+        for row in input_data:
+            row['last'], row['first'] = row['name'].split(', ')
+            del row['name']
+            writer.writerow(row)
+
+
+if __name__ == "__main__":
+    main()

BIN
problems/pset6/shirt/after1.jpg


BIN
problems/pset6/shirt/after2.jpg


BIN
problems/pset6/shirt/after3.jpg


BIN
problems/pset6/shirt/before1.jpg


BIN
problems/pset6/shirt/before2.jpg


BIN
problems/pset6/shirt/before3.jpg


BIN
problems/pset6/shirt/shirt.png


+ 43 - 0
problems/pset6/shirt/shirt.py

@@ -0,0 +1,43 @@
+import os
+import sys
+
+import PIL
+from PIL import Image
+
+
+def main():
+    try:
+        input_file_name, output_file_name = sys.argv[1:3]
+    except (IndexError, ValueError):
+        print("Too few command-line arguments")
+        sys.exit(1)
+    if len(sys.argv) > 3:
+        print("Too many command-line arguments")
+        sys.exit(1)
+    if not os.path.isfile(input_file_name):  # Or try opening and catch FileNotFoundError
+        print(f"Input does not exist")
+        sys.exit(1)
+    if not (any(input_file_name.lower().endswith(format) for format in (".jpg", ".jpeg", ".png")) and any(output_file_name.lower().endswith(format) for format in (".jpg", ".jpeg", ".png"))):
+        print(f"Invalid input")
+        sys.exit(1)
+    if (input_file_name[-4] == '.' and input_file_name[-4:] != output_file_name[-4:]) or (input_file_name[-3] == '.' and input_file_name[-3:] != output_file_name[-3:]):
+        print(f"Input and output have different extensions")
+        sys.exit(1)
+    overlap_t_shirt(input_file_name, output_file_name)
+    """Open the input with Image.open, per pillow.
+    resize and crop the input with ImageOps.fit, per pillow.readthedocs.io/en/stable/reference/ImageOps.html#PIL.ImageOps.fit,
+    using default values for method, bleed, and centering, overlay the shirt with Image.paste,
+    per pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.paste,
+    and save the result with Image.save, per pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save."""
+
+
+def overlap_t_shirt(input_file_name, output_file_name):
+    image = Image.open(input_file_name)
+    shirt = Image.open('shirt.png')
+    image = PIL.ImageOps.fit(image, size=shirt.size)
+    image.paste(shirt, mask=shirt)
+    image.save(output_file_name)
+
+
+if __name__ == "__main__":
+    main()

+ 19 - 0
problems/pset7/numb3rs/numb3rs.py

@@ -0,0 +1,19 @@
+import re
+import sys
+
+
+def main():
+    print(validate(input("IPv4 Address: ")))
+
+
+def validate(ip):
+    if not re.match(r'^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$', ip):
+        return False
+    for n in map(int, ip.split('.')):
+        if not 0 <= n <= 255:
+            return False
+    return True
+
+
+if __name__ == "__main__":
+    main()

+ 32 - 0
problems/pset7/numb3rs/test_numb3rs.py

@@ -0,0 +1,32 @@
+from numb3rs import validate
+
+
+def main():
+    test_non_digits()
+    test_negative_numbers()
+    test_numbers_too_big()
+    test_numbers_too_long()
+
+
+def test_non_digits():
+    assert validate('aaa.bbb.ccc.ddd') == False
+    assert validate('cat') == False
+
+
+def test_negative_numbers():
+    assert validate('-11.111.-1.-1') == False
+
+
+def test_numbers_too_big():
+    assert validate('257.200.1.1') == False
+    assert validate('200.257.1.1') == False
+    assert validate('200.1.257.1') == False
+    assert validate('200.1.5.257') == False
+
+
+def test_numbers_too_long():
+    assert validate('200.1.5.2.57') == False
+
+
+if __name__ == '__main__':
+    main()

+ 23 - 0
problems/pset7/response/response.py

@@ -0,0 +1,23 @@
+from validator_collection import validators
+
+
+def main():
+  if validate_email(input("What's your email address?\t\t")):
+    print("Valid")
+  else:
+    print("Invalid")
+
+
+def validate_email(email: str) -> bool:
+  try:
+    email_address = validators.email(email, allow_empty = True)
+  except Exception as e:
+    email_address = None
+  if not email_address:
+    return False
+  return True
+
+
+if __name__ == '__main__':
+  main()
+

+ 16 - 0
problems/pset7/response/test_response.py

@@ -0,0 +1,16 @@
+from response import validate_email
+
+
+def main():
+  test_cs50p()
+
+
+def test_cs50p():
+  assert validate_email("malan@harvard.edu")== True
+  assert validate_email("info@cheznadi.com")== True
+  assert validate_email("malan@@@harvard.edu")== False
+  assert validate_email("info@cheznadi..com")== False
+
+
+if __name__ == "__main__":
+  main()

+ 37 - 0
problems/pset7/um/test_um.py

@@ -0,0 +1,37 @@
+from um import count
+
+def main():
+  test_count_ums()
+  test_words_containing_ums()
+  test_punctuation()
+  test_from_cs50p()
+
+
+def test_count_ums():
+  assert count("Hello, um, world") == 1
+  assert count("Hello um, um, world um") == 3
+  assert count("Um Hello, um, world um") == 3
+
+
+def test_words_containing_ums():
+  assert count("instrumentation") == 0
+  assert count("umbrella") == 0
+  assert count("momentum") == 0
+
+
+def test_punctuation():
+  assert count("Hello um?") == 1
+  assert count("Hello um!") == 1
+  assert count("Hello um") == 1
+  assert count("Hello um.") == 1
+
+
+def test_from_cs50p():
+  assert count("um") == 1
+  assert count("um?") == 1
+  assert count("Um, thanks for the album.") == 1
+  assert count("Um, thanks, um...") == 2
+
+
+if __name__ == '__main__':
+  main()

+ 15 - 0
problems/pset7/um/um.py

@@ -0,0 +1,15 @@
+import re
+
+um_regex = re.compile(r"\bum\b", re.IGNORECASE)
+
+
+def main():
+    print(count(input("Text: ")))
+
+
+def count(s):
+    return len(um_regex.findall(s))
+
+
+if __name__ == "__main__":
+    main()

+ 20 - 0
problems/pset7/watch/watch.py

@@ -0,0 +1,20 @@
+import re
+
+youtube_regex = re.compile(r"<iframe.*https?://(?:www\.)?youtube\.com/embed/(\w+).*</iframe>")
+
+
+def main():
+    print(parse(input("HTML: ")))
+
+
+def parse(s):
+    video_id = youtube_regex.search(s)
+    if video_id:
+        video_id = video_id.groups()[0]
+    else:
+        return None
+    return f"https://youtu.be/{video_id}"
+
+
+if __name__ == "__main__":
+    main()

+ 102 - 0
problems/pset7/working/test_working.py

@@ -0,0 +1,102 @@
+import pytest
+
+from working import convert
+
+
+def main():
+    test_missing_to()
+    test_out_of_range_times()
+    test_exception()
+    test_hours_off_by_one()
+
+
+def test_missing_to():
+    try:
+        convert('09:00 AM - 05:00 PM')
+    except ValueError:
+        pass
+    else:
+        raise ValueError("Missing to")
+    try:
+        convert('09:00 AM 05:00 PM')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+
+
+def test_out_of_range_times():
+    try:
+        convert('09:00 AM to 05:61 PM')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert('31:00 AM to 05:00 PM')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert('13:00 PM to 8:00 AM')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert('13:00PM to 8:00 AM')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert('13:00 to 8:00')
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("09:00 AM - 17:00 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("9:00 AM 5:00 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("9:60 AM to 5:60 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("9 AM - 5 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("9 AM5 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    try:
+        convert("9 AM 5 PM")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+    with pytest.raises(ValueError):
+        convert("9:72 to 6:30")
+
+
+def test_exception():
+    with pytest.raises(ValueError):
+        convert("09:00 AM - 17:00 PM")
+    with pytest.raises(ValueError):
+        convert("9:00 AM 5:00 PM")
+    with pytest.raises(ValueError):
+        convert("9:60 AM to 5:60 PM")
+    with pytest.raises(ValueError):
+        convert("9 AM - 5 PM")
+    with pytest.raises(ValueError):
+        convert("9 AM5 PM")
+    with pytest.raises(ValueError):
+        convert("9 AM 5 PM")
+    with pytest.raises(ValueError):
+        convert("9:72 to 6:30")
+
+
+def test_hours_off_by_one():
+    assert convert('12:00 AM to 12:00 PM') == "00:00 to 12:00"
+    assert convert("9 AM to 5 PM") == "09:00 to 17:00"
+    assert convert("8 PM to 8 AM") == "20:00 to 08:00"
+    assert convert("8:00 PM to 8:00 AM") == "20:00 to 08:00"
+    assert convert("12 AM to 12 PM") == "00:00 to 12:00"
+    assert convert("12:00 AM to 12:00 PM") == "00:00 to 12:00"
+
+
+if __name__ == '__main__':
+    main()

+ 39 - 0
problems/pset7/working/working.py

@@ -0,0 +1,39 @@
+import re
+
+
+american_time_regex = re.compile(r"^\s*(\d+)(?:\:(\d{2}))? ([AP])M to (\d+)(?:\:(\d{2}))? ([AP])\s*M$")
+
+
+def main():
+    print(convert(input("Hours: ")))
+
+
+def convert(s):
+    time = american_time_regex.match(s)
+    if time is None:
+        raise ValueError("Invalid input time")
+    if len(time.groups()) == 4:
+        start_h, start_ampm, end_h, end_ampm = time.groups()
+        start_m, end_m = 0, 0
+    else:
+        start_h, start_m, start_ampm, end_h, end_m, end_ampm = time.groups()
+        start_m, end_m = map(lambda x: int(x) if x else 0, (start_m, end_m))
+    start_h, end_h = map(int, (start_h, end_h))
+    if start_h > 12 or end_h > 12:
+        raise ValueError
+    if (start_ampm == 'P' and start_h != 12) or (start_ampm == 'A' and start_h == 12):
+        start_h += 12
+    if (end_ampm == 'P' and end_h != 12) or (end_ampm == 'A' and end_h == 12):
+        end_h += 12
+    if start_h > 24 or end_h > 24 or start_m > 59 or end_m > 59:
+        raise ValueError
+    start_h = start_h % 24
+    end_h = end_h % 24
+    return f"{start_h:0>2}:{start_m:0>2} to {end_h:0>2}:{end_m:0>2}"
+
+
+...
+
+
+if __name__ == "__main__":
+    main()

+ 26 - 0
problems/pset8/jar/jar.py

@@ -0,0 +1,26 @@
+class Jar:
+    def __init__(self, capacity=12):
+        if not isinstance(capacity, int) or capacity < 0:
+            raise ValueError("Invalid capacity")
+        self._capacity = capacity
+        self._size = 0
+
+    def __str__(self):
+        return '🍪' * self.size
+
+    def deposit(self, n):
+        new_size = n + self.size
+        if new_size > self.capacity or new_size < 0:
+            raise ValueError("Exceeded capacity")
+        self._size += n
+
+    def withdraw(self, n):
+        return self.deposit(-n)
+
+    @property
+    def capacity(self):
+        return self._capacity
+
+    @property
+    def size(self):
+        return self._size

+ 46 - 0
problems/pset8/jar/test_jar.py

@@ -0,0 +1,46 @@
+from jar import Jar
+
+
+def main():
+    test_capacity()
+    test_size()
+    test_deposit()
+    test_withdraw()
+
+
+def test_capacity():
+    j = Jar(capacity=3)
+    assert j.capacity == 3
+
+
+def test_size():
+    j = Jar(capacity=5)
+    assert j.size == 0
+    j.deposit(3)
+    j.withdraw(1)
+    assert j.size == 2
+
+
+def test_deposit():
+    j = Jar(capacity=5)
+    j.deposit(3)
+    try:
+        j.deposit(3)
+        raise Exception("Deposit n > capacity did not raise ValueError")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+
+
+def test_withdraw():
+    j = Jar(capacity=5)
+    j.deposit(3)
+    j.withdraw(2)
+    try:
+        j.withdraw(2)
+        raise Exception("Withdraw n > size did not raise ValueError")
+    except Exception as e:
+        assert isinstance(e, ValueError)
+
+
+if __name__ == '__main__':
+    main()

+ 39 - 0
problems/pset8/seasons/seasons.py

@@ -0,0 +1,39 @@
+import datetime
+import re
+import sys
+
+import inflect
+
+date_regex = re.compile(r"\d{4}-\d{2}-\d{2}")
+p = inflect.engine()
+
+
+def main():
+  today = datetime.date.today()
+  users_date = input("Date of Birth: ")
+  try:
+    users_date = parse_date(users_date)
+  except ValueError:
+    sys.exit(1)
+  minutes = get_minutes(users_date, today)
+  print(format_minutes(minutes))
+
+
+def get_minutes(start_date, end_date):
+  minutes = (end_date - start_date).total_seconds() / 60
+  return int(round(minutes, 0))
+
+
+def format_minutes(minutes: int) -> str:
+  stringed_number = p.number_to_words(minutes, andword="").capitalize()
+  return f"{stringed_number} minute{'s' if minutes > 1 else ''}"
+
+
+def parse_date(date_string):
+  if not date_regex.match(date_string):  # Acutally not necessary: strptime would raise ValueError anyway
+    raise ValueError
+  return datetime.datetime.strptime(date_string, '%Y-%m-%d').date()
+
+
+if __name__ == '__main__':
+  main()

+ 44 - 0
problems/pset8/seasons/test_seasons.py

@@ -0,0 +1,44 @@
+from seasons import format_minutes, parse_date
+
+
+def main():
+  test_format_minutes()
+  test_invalid_dates()
+  test_known_intervals()
+
+
+def test_format_minutes():
+  assert format_minutes(1) == 'One minute'
+  assert format_minutes(2) == 'Two minutes'
+
+
+def test_invalid_dates():
+  try:
+    parse_date('91-5-9')
+    raise Exception
+  except Exception as e:
+    assert isinstance(e, ValueError)
+  try:
+    parse_date('cacao')
+    raise Exception
+  except Exception as e:
+    assert isinstance(e, ValueError)
+  try:
+    parse_date('1991-13-09')
+    raise Exception
+  except Exception as e:
+    assert isinstance(e, ValueError)
+  try:
+    parse_date('1991-11-40')
+    raise Exception
+  except Exception as e:
+    assert isinstance(e, ValueError)
+
+
+def test_known_intervals():
+  assert format_minutes(525600) == "Five hundred twenty-five thousand, six hundred minutes"
+  assert format_minutes(1051200) == "One million, fifty-one thousand, two hundred minutes"
+
+
+if __name__ == '__main__':
+  main()

BIN
problems/pset8/shirtificate/shirtificate.pdf


BIN
problems/pset8/shirtificate/shirtificate.png


+ 22 - 0
problems/pset8/shirtificate/shirtificate.py

@@ -0,0 +1,22 @@
+from fpdf import FPDF
+
+
+def main():
+    username = input("What's your name?\t\t")
+    pdf = FPDF(orientation="P", unit="mm", format="A4")
+    pdf.set_auto_page_break(False)
+    pdf.add_page()
+    pdf.set_font("helvetica", "B", 45)
+    pdf.cell(0, 10, 'CS50 Shirtificate', new_x="LMARGIN", new_y="NEXT", align='C')
+    page_width = pdf.w
+    total_horizontal_margin = 0.2 * page_width
+    image_width = pdf.w-total_horizontal_margin
+    pdf.image("shirtificate.png", x=total_horizontal_margin/2, y=50, w=image_width)
+    pdf.set_font("helvetica", "B", 32)
+    pdf.set_text_color(255, 255, 255)
+    pdf.cell(0, 230, f'{username} took CS50', new_x="LMARGIN", new_y="NEXT", align='C')
+    pdf.output("shirtificate.pdf")
+
+
+if __name__ == '__main__':
+    main()