diff --git a/.gitignore b/.gitignore index b6e47617d..4b492eb54 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ + +# PyCharm +.idea \ No newline at end of file diff --git a/Work/bounce.py b/Work/bounce.py index 3660ddd82..a0c5bab84 100644 --- a/Work/bounce.py +++ b/Work/bounce.py @@ -1,3 +1,12 @@ # bounce.py # # Exercise 1.5 + +height = 100 +bounce = 0 + +while bounce < 10: + height = height * (3 / 5) + bounce = bounce + 1 + roundedHeight = round(height * 10000) / 10000 + print(bounce, roundedHeight) diff --git a/Work/fileparse.py b/Work/fileparse.py index 1d499e733..ec992aa42 100644 --- a/Work/fileparse.py +++ b/Work/fileparse.py @@ -1,3 +1,67 @@ # fileparse.py # # Exercise 3.3 + +import csv +from pprint import pprint + + +def parse_csv(lines, select=None, types=None, has_headers=True, silence_errors=True, delimiter=','): + """ + Parse a CSV file into a list of records + """ + if not has_headers and select: + raise RuntimeError("wrong combination of args") + + rows = csv.reader(lines, delimiter=delimiter) + + # Read the file headers + if has_headers: + headers = next(rows) + else: + headers = None + + # If a column selector was given, find indices of the specified columns. + # Also narrow the set of headers used for resulting dictionaries + if select: + indices = [headers.index(colname) for colname in select] + headers = select + else: + indices = [] + + records = [] + for index, row in enumerate(rows): + try: + if not row: # Skip rows with no data + continue + + # Filter the row if specific columns were selected + if indices: + row = [row[index] for index in indices] + + if types: + row = [func(val) for func, val in zip(types, row)] + + if headers: + record = dict(zip(headers, row)) + else: + record = tuple(row) + + records.append(record) + except ValueError as e: + if not silence_errors: + print(f"Row {index + 1}: Couldn't convert", row) + print(f"Row {index + 1}: Reason", e) + pass + + return records + + +def main(): + with open('Data/portfolio.csv', 'rt') as f: + portfolio = parse_csv(f, types=[str, int, float], silence_errors=False) + pprint(portfolio) + + +if __name__ == '__main__': + main() diff --git a/Work/files.py b/Work/files.py new file mode 100644 index 000000000..1902d5c9a --- /dev/null +++ b/Work/files.py @@ -0,0 +1,8 @@ +# with open("Data/portfolio.csv", "rt") as f: +# for line in f: +# print(line, end="") + +import gzip +with gzip.open("Data/portfolio.csv.gz", "rt") as f: + for line in f: + print(line, end="") diff --git a/Work/generators.py b/Work/generators.py new file mode 100644 index 000000000..9e556e95f --- /dev/null +++ b/Work/generators.py @@ -0,0 +1,27 @@ +from random import random +from time import sleep + + +def generate_number(): + while True: + sleep(0.5) + yield random() + + +def filter_numbers(numbers): + for n in numbers: + if n > 0.5: + yield n + +# Generator expression variant: +# def filter_numbers(numbers): +# return (x for x in numbers if x > 0.5) + + +n = generate_number() +f = filter_numbers(n) +for i, n in enumerate(f): + if i > 10: + break + else: + print(f"{i:>2}: {n}") diff --git a/Work/lists.py b/Work/lists.py new file mode 100644 index 000000000..a2470ef5f --- /dev/null +++ b/Work/lists.py @@ -0,0 +1,17 @@ +# lists.py +# Exercise 1.2x + +mylist = ["a", "b", "c"] +if "x" not in mylist: + print("Yo") + +mylist.append("bla") +mylist.sort(reverse=True) + +print(",".join(mylist)) + +nums = [101, 102, 103] +items = ["spam", mylist, nums] +print(items) +print(items[1][-1]) +print(items[0][-1]) diff --git a/Work/mortgage.py b/Work/mortgage.py index d527314e3..1469429fe 100644 --- a/Work/mortgage.py +++ b/Work/mortgage.py @@ -1,3 +1,31 @@ # mortgage.py # # Exercise 1.7 + +extra_payment_start_month = 61 +extra_payment_end_month = 108 +extra_payment = 1000 + +principal = 500000.0 +rate = 0.05 +payment = 2684.11 +total_paid = 0.0 +month = 0 + +while principal > 0: + month = month + 1 + if extra_payment_start_month <= month <= extra_payment_end_month: + monthly_extra = extra_payment + else: + monthly_extra = 0 + + principal = principal * (1 + rate / 12) - payment - monthly_extra + total_paid = total_paid + payment + monthly_extra + if principal < 0: + total_paid = total_paid - abs(principal) + principal = 0 + + print(f'{month:3} {total_paid:9.2f} {principal:9.2f}') + +total_paid_formatted = '{:,}'.format(round(total_paid, 2)) +print(f'\nTotal paid {total_paid_formatted} over {month} months') diff --git a/Work/pcost.py b/Work/pcost.py index e68aa20b4..42955c71a 100644 --- a/Work/pcost.py +++ b/Work/pcost.py @@ -1,3 +1,20 @@ # pcost.py # # Exercise 1.27 + +import sys +from Work.fileparse import parse_csv + + +def portfolio_cost(filename): + with open(filename, "rt") as f: + return parse_csv(f, select=["shares", "price"], types=[int, float], silence_errors=True) + + +if len(sys.argv) == 2: + filename = sys.argv[1] +else: + filename = "Data/portfolio.csv" + +cost = portfolio_cost(filename) +print("Total cost:", cost) diff --git a/Work/report.py b/Work/report.py index 47d5da7b1..f27f21d8b 100644 --- a/Work/report.py +++ b/Work/report.py @@ -1,3 +1,74 @@ +#!/usr/bin/env python3 # report.py # # Exercise 2.4 +from pprint import pprint + +from Work import tableformat +from Work.fileparse import parse_csv +from Work.stock import Stock + + +def read_portfolio(filename): + with open(filename, "rt") as f: + dicts = parse_csv(f, types=[str, int, float]) + return [Stock(**p) for p in dicts] + + +def read_prices(filename): + with open(filename, "rt") as f: + prices = parse_csv(f, types=[str, float], has_headers=False, silence_errors=True) + return dict(prices) + + +def make_report(portfolio, prices): + rows = [] + for p in portfolio: + name = p.name + shares = p.shares + price = p.price + current_price = prices[name] + change = current_price - price + rows.append((name, shares, current_price, change)) + return rows + + +def print_report(reportdata, formatter): + """ + Print a nicely formated table from a list of (name, shares, price, change) tuples. + """ + formatter.headings(['Name', 'Shares', 'Price', 'Change']) + for name, shares, price, change in reportdata: + rowdata = [name, str(shares), f'{price:0.2f}', f'{change:0.2f}'] + formatter.row(rowdata) + + +def portfolio_report(portfoliofile, pricefile, fmt='txt'): + """ + Make a stock report given portfolio and price data files. + """ + # Read data files + portfolio = read_portfolio(portfoliofile) + prices = read_prices(pricefile) + + # Create the report data + report = make_report(portfolio, prices) + + # Print it out + formatter = tableformat.create_formatter(fmt) + print_report(report, formatter) + + +def main(argv): + portfolio = read_portfolio('Data/portfolio.csv') + portfolio.sort(key=lambda s: s.shares) + pprint(portfolio) + + # portfolio_report('Data/portfolio.csv', 'Data/prices.csv') + # portfolio_report(argv[0], argv[1]) + + +if __name__ == '__main__': + import sys + + main(sys.argv) diff --git a/Work/sears.py b/Work/sears.py new file mode 100644 index 000000000..8add59254 --- /dev/null +++ b/Work/sears.py @@ -0,0 +1,15 @@ +# sears.py + +bill_thickness = 0.11 * 0.001 # Meters (0.11 mm) +sears_height = 442 # Height (meters) +num_bills = 1 +day = 1 + +while num_bills * bill_thickness < sears_height: + print(day, num_bills, num_bills * bill_thickness) + day = day + 1 + num_bills = num_bills * 2 + +print('Number of days', day) +print('Number of bills', num_bills) +print('Final height', num_bills * bill_thickness) \ No newline at end of file diff --git a/Work/stock.py b/Work/stock.py new file mode 100644 index 000000000..cb46d74df --- /dev/null +++ b/Work/stock.py @@ -0,0 +1,36 @@ +from Work.typedproperty import StringProp, IntProp, FloatProp + + +class Stock: + name = StringProp('name') + shares = IntProp('shares') + price = FloatProp('price') + + def __init__(self, name: str, shares: int, price: float): + self.name = name + self.shares = shares + self.price = price + + @property + def cost(self) -> float: + return self.shares * self.price + + def sell(self, amount: int): + self.shares = max(self.shares - amount, 0) + + def __repr__(self): + return f"Stock('{self.name}', {self.shares}, {self.price})" + + +def main(): + s = Stock("bla", 100, 9.9) + print(s) + with open("Data/portfolio.csv", "rt") as lines: + from Work.fileparse import parse_csv + portdicts = parse_csv(lines, select=['name', 'shares', 'price'], types=[str, int, float]) + portfolio = [Stock(p["name"], p["shares"], p["price"]) for p in portdicts] + print(sum([s.cost for s in portfolio])) + + +if __name__ == "__main__": + main() diff --git a/Work/tableformat.py b/Work/tableformat.py new file mode 100644 index 000000000..8ce2450c8 --- /dev/null +++ b/Work/tableformat.py @@ -0,0 +1,68 @@ +class TableFormatter: + def headings(self, headers): + """ + Emit the table headings. + """ + raise NotImplementedError() + + def row(self, rowdata): + """ + Emit a single row of table data. + """ + raise NotImplementedError() + + +class TextTableFormatter(TableFormatter): + """ + Emit a table in plain-text format + """ + + def headings(self, headers): + for h in headers: + print(f'{h:>10s}', end=' ') + print() + print(('-' * 10 + ' ') * len(headers)) + + def row(self, rowdata): + for d in rowdata: + print(f'{d:>10s}', end=' ') + print() + + +class CSVTableFormatter(TableFormatter): + """ + Output portfolio data in CSV format. + """ + + def headings(self, headers): + print(','.join(headers)) + + def row(self, rowdata): + print(','.join(rowdata)) + + +class HTMLTableFormatter(TableFormatter): + """ + Output portfolio data in HTML format. + """ + + def headings(self, headers): + print(f"