diff --git a/students/johnwachter/Final_Project/DataModel.py b/students/johnwachter/Final_Project/DataModel.py new file mode 100644 index 0000000..1b292a9 --- /dev/null +++ b/students/johnwachter/Final_Project/DataModel.py @@ -0,0 +1,126 @@ +from datetime import datetime +import re as rex + +class Product(object): + def __init__(self, product_id: int, product_name: str): + self.__product_id = product_id + self.__product_name = product_name + def __str__(self): + return("ProductID: {}, ProductName: {}".format(self.__product_id, self.__product_name)) + def __repr__(self): + return "{i}:[{brk1}{pi}{p}, {c}:{cv}{brk2}]".format(brk1='{', i="Product", + pi="'ProductID':", + p=self.__product_id, + c="'ProductName'", + cv=self.__product_name, brk2='}') + def __dict__(self): + return {"ProductID": self.__product_id, "ProductName": self.__product_name} + @property + def product_id(self): + return self.__product_id + def set_product_id(self, product_id): + if type(product_id) is not int: + raise TypeError("Requires integer!") + if product_id <= 0: + raise ValueError("Requires value greater than zero!") + else: + self.__product_id = product_id + @property + def product_name(self): + return self.__product_name + def set_product_name(self, product_name): + self.__product_name = str(product_name).strip() + +class InventoryCount(object): + def __init__(self, product: Product, count: int): + self.__product = product + self.__count = count + def __str__(self): + return ("Product: ({}), Number of products: {}".format(self.__product, self.__count)) + def __repr__(self): + return "{i}:[{p}, {c}:{cv}]".format(i="InventoryCount", + p=self.product.__repr__(), + c="count", + cv=self.count) + def __dict__(self): + return {"Product": self.__product, "Count": self.__count} + @property + def product(self): + return self.__product + def set_product(self, product): + if type(product) is not Product: raise TypeError("Requires Product Object!") + else: + self.__product = product + @property + def count(self): + return self.__count + def set_count(self, count): + if count < 0: + raise ValueError("Negative counts are not possible") + self.__count = count + +class Inventory(object): + def __init__(self, inventory_id: int, inventory_date: datetime.date, inventory_counts: InventoryCount = [None]): + self.__inventory_id = inventory_id + self.__inventory_date = inventory_date + self.__inventory_counts = inventory_counts + def __str__(self): + return("InventoryID: {}, InventoryDate: {}, InventoryCount: {}".format(self.__inventory_id, self.__inventory_date, self.__inventory_counts)) + def __repr__(self): + return "{d}, {i}:[{p}, {c}:{cv}]".format(d=self.__inventory_date, + i="InventoryID", + p=self.__inventory_id.__repr__(), + c="count", + cv=self.__inventory_counts.__repr__()) + def __dict__(self): + return {"InventoryID": self.__inventory_id, "InventoryDate": self.__InventoryDate, "InventoryCount": self.__inventory_counts} + @property + def inventory_id(self): + return self.__inventory_id + def set_inventory_id(self, inventory_id): + if inventory_id < 0: + raise TypeError("Can't have a negative InventoryID!") + else: + self.__inventory_id = inventory_id + @property + def inventory_date(self): + return self.__inventory_date + def set_inventory_date(self, inventory_date): + try: + if rex.match("\d\d\d\d-\d\d-\d\d", str(inventory_date)) is None: + raise TypeError("Not a Date! Use the following format: YYYY-MM-DD") + else: + self.__inventory_date = inventory_date + except Exception as e: + raise e + @property + def inventory_counts(self): + return self.__inventory_counts + def set_inventory_counts(self, inventory_counts): + if inventory_counts is not None and type(inventory_counts) is InventoryCount: + self.inventory_counts = inventory_counts + +if __name__ == '__main__': + p1 = Product(100, "ProdA") + p2 = Product(200, "ProdB") + #print("P1 str function produces:\n{}".format(p1)) + #print(p2.product_id) + #print(repr(p1)) + p2.set_product_id(1) + #print(p2.product_id) + ic1 = InventoryCount(p1, 15) + ic2 = InventoryCount(p2, 45) + #print(repr(ic1)) + #print("ic1 str function produces:\n{}".format(ic1)) + invJan0119 = Inventory(1, '2019-01-01', [ic1,ic2]) + invFeb0119 = Inventory(2, '2020-01-01', [ic1]) + print("repr func produces: {}".format(repr(invFeb0119))) + print("Str func produces: {}".format(invFeb0119)) + print("repr func produces: {}".format(repr(invJan0119))) + print("Str func produces: {}".format(invJan0119)) + #for ic in invJan0119.inventory_counts: + #print(invJan0119.inventory_date , ic.product.product_name, ' = ', ic.count) + invJan0119.set_inventory_date('2019-09-01') + #print(invJan0119.inventory_date) + print(dict()) + diff --git a/students/johnwachter/Final_Project/DataProcessor.py b/students/johnwachter/Final_Project/DataProcessor.py new file mode 100644 index 0000000..2d9a2c8 --- /dev/null +++ b/students/johnwachter/Final_Project/DataProcessor.py @@ -0,0 +1,294 @@ +import sqlite3 +from sqlite3 import Error as sqlErr +import re as rex + +def create_connection(db_file: str ="C:\sqlite\sqlite\Python210FinalDB.db"): + """ Create or connect to a SQLite database """ + try: + con = sqlite3.connect(db_file) + except sqlErr as se: + raise Exception('SQL Error in create_connection(): ' + se.__str__()) + except Exception as e: + raise Exception('General Error in create_connection(): ' + e.__str__()) + return con + +# SQL Validators +def check_for_extra_semicolon(sql_str): + """Checks for an extra semicolon""" + # print(len("Select;Delete From T1; ID, Name FROM T1;".split(';')) > 2) + try: + if len(sql_str.split(';')) > 2: + raise sqlErr("Extra Semi-Colon Detected!") + except Exception as e: + raise e + + +def check_for_or(sql_str): + """Checks for an injected OR in tampered WHERE Clause""" + # print(rex.search("WHERE", "SELECT * FROM T1 WHERE", rex.IGNORECASE)) + # print(rex.search("or","FROM T1 WHERE ID = 1 or 1 = 1".split('WHERE')[1], rex.IGNORECASE)) + try: + if rex.search("WHERE", sql_str, rex.IGNORECASE): #If it has a Where clause + if rex.search(' or ', sql_str.split('WHERE')[1], rex.IGNORECASE) is not None: # check injected OR + raise sqlErr("OR Detected!") + except Exception as e: + raise e + +def check_for_date(date_str): #Inventories table check + try: + if rex.match("\d\d\d\d-\d\d-\d\d", str(date_str)) is None: # Returns None if not matched + raise sqlErr("Not a Date!") + except Exception as e: + raise e + +# def execute_sql_code(db_con: object = None, sql_code: str = ''): +# """ Execute SQL code on a open connection """ +# try: +# if db_con is not None and sql_code != '': +# # Validate +# check_for_extra_semicolon(sql_code); +# check_for_or(sql_code); +# # Connect and Run +# with db_con: +# csr = db_con.cursor() +# csr.execute(sql_code) +# else: +# raise Exception('SQL Code or Connection is missing!') +# except sqlite3.OperationalError as oe: +# raise Exception('Table already exists(): ' + oe.__str__()) +# except sqlErr as se: +# raise Exception('SQL Error in execute_sql_code(): ' + se.__str__() + 'That ID already exists, and ID is a primary key') +# except Exception as e: +# raise Exception('General Error in execute_sql_code(): ' + e.__str__()) +# return csr + + +class DBProcessor(object): + def __init__(self, db_name: str = "C:\sqlite\sqlite\Python210FinalDB.db"): + self.__db_name = db_name + self.__db_con = self.create_connection(self.db_name) + + @property + def db_name(self): # Get DB Name + return self.__db_name + + @property + def db_con(self): # Get Live Connection + return self.__db_con + + # SQL Validators + @staticmethod + def check_for_extra_semicolon(sql_str): + """Checks for an extra semicolon""" + # print(len("Select;Delete From T1; ID, Name FROM T1;".split(';')) > 2) + try: + if len(sql_str.split(';')) > 2: + raise sqlErr("Extra Semi-Colon Detected!") + except Exception as e: + raise e + + @staticmethod + def check_for_or(sql_str): + """Checks for an injected OR in tampered WHERE Clause""" + # print(rex.search("WHERE", "SELECT * FROM T1 WHERE", rex.IGNORECASE)) + # print(rex.search("or","FROM T1 WHERE ID = 1 or 1 = 1".split('WHERE')[1], rex.IGNORECASE)) + try: + if rex.search("WHERE", sql_str, rex.IGNORECASE): # If it has a Where clause + if rex.search(' or ', sql_str.split('WHERE')[1], rex.IGNORECASE) is not None: # injected OR? + raise sqlErr("OR Detected!") + except Exception as e: + raise e + + @staticmethod + def check_for_date(date_str): # Inventories table check + """Checks for an valid date string""" + try: + if rex.match("\d\d\d\d-\d\d-\d\d", str(date_str)) is None: + raise sqlErr("Not a Date!") + except Exception as e: + raise e + + @staticmethod + def check_for_product_name(product_name): # Product table check + """Checks to make sure the product name is a string""" + try: + if type(product_name) is not str: + raise sqlErr("Not a valid product name!") + except Exception as e: + raise e + + @staticmethod + def inventory_counts_check(inventory_counts): + """Checks something in the inventory counts""" + pass + + def create_connection(self, db_file: str="C:\sqlite\sqlite\Python210FinalDB.db"): + """ Create or connect to a SQLite database """ + try: + con = sqlite3.connect(db_file) + except sqlErr as se: + raise Exception('SQL Error in create_connection(): ' + se.__str__()) + except Exception as e: + raise Exception('General Error in create_connection(): ' + e.__str__()) + return con + + def execute_sql_code(self, db_con: object = None, sql_code: str = ''): + """ Execute SQL code on a open connection """ + db_con = self.db_con + try: + if db_con is not None and sql_code != '': + # Validate + self.check_for_extra_semicolon(sql_code); + self.check_for_or(sql_code); + # Connect and Run + with db_con: + csr = db_con.cursor() + csr.execute(sql_code) + else: + raise Exception('SQL Code or Connection is missing!') + except sqlErr as se: + raise Exception('SQL Error in execute_sql_code(): ' + se.__str__()) + except Exception as e: + raise Exception('General Error in execute_sql_code(): ' + e.__str__()) + return csr + + def build_ins_code(self): + # Validate Input + sql = str.format("INSERT Not Implemented Yet") + return sql + + def build_upd_code(self): + # Validate Input + sql = str.format("UPDATE Not Implemented Yet") + return sql + + def build_del_code(self): + # Validate Input + # Validate Input + sql = str.format("DELETE Not Implemented Yet") + return sql + + def build_sel_code(self): + # Validate Input + sql = str.format("SELECT Not Implemented Yet") + return sql + + +class InventoryProcessor(DBProcessor): + def build_ins_code(self, inventory_id: int, inventory_date: str): + DBProcessor.check_for_date(inventory_date) + sql = str.format("INSERT OR IGNORE INTO Inventories (InventoryID, InventoryDate) " + "VALUES ({id},'{date}');", id=inventory_id, date=inventory_date) + return sql + + def build_upd_code(self, inventory_id: int, inventory_date: str): + DBProcessor.check_for_date(inventory_date) + sql = str.format("UPDATE Inventories SET InventoryDate = '{date}' " + "WHERE InventoryID = {id};", id=inventory_id, date=inventory_date) + return sql + + def build_del_code(self, inventory_id: int): + sql = str.format("DELETE FROM Inventories " + "WHERE InventoryID = {id};", id=inventory_id) + return sql + + def build_sel_code(self, inventory_id: int = None): + if inventory_id is not None: + w = ' WHERE InventoryID = ' + str(inventory_id) + else: + w = '' + sql = str.format("SELECT InventoryID, InventoryDate " + "FROM Inventories{WHERE};", WHERE=w) + return sql + +class ProductsProcessor(DBProcessor): + def build_ins_code(self, product_id: int, product_name: str): + DBProcessor.check_for_product_name(product_name) + sql = str.format("INSERT OR IGNORE INTO Products (ProductID, ProductName) " + "VALUES ({id},'{name}');", id=product_id, name=product_name) + return sql + + def build_upd_code(self, product_id: int, product_name: str): + DBProcessor.check_for_product_name(product_name) + sql = str.format("UPDATE Products SET ProductName = '{name}' " + "WHERE ProductID = {id};", id=product_id, name=product_name) + return sql + + def build_del_code(self, product_id: int): + sql = str.format("DELETE FROM Products WHERE ProductID = {id};", id=product_id) + return sql + + def build_sel_code(self, product_id: int = None): + if product_id is not None: + w = ' WHERE ProductID = ' + str(product_id) + else: + w = '' + sql = str.format("SELECT ProductID, ProductName " + "FROM Products{WHERE};", WHERE=w) + return sql + +class InventoryCountsProcessor(DBProcessor): + def build_ins_code(self, inventory_id: int, product_id: int, inventory_count: int): + DBProcessor.inventory_counts_check(inventory_count) + sql = str.format("INSERT OR IGNORE INTO InventoryCounts (InventoryID, ProductID, Count) " + "VALUES ({invid}, {prodid}, '{count}');", invid=inventory_id, prodid=product_id, count=inventory_count) + return sql + + def build_upd_code(self, inventory_id: int, product_id: int, inventory_count): + DBProcessor.inventory_counts_check(inventory_count) + sql = str.format("UPDATE InventoryCounts SET Count = '{count}' " + "WHERE ProductID = {prodid} AND InventoryID = {invid};", count=inventory_count, prodid=product_id, invid=inventory_id) + return sql + + def build_del_code(self, inventory_id: int, product_id: int): + sql = str.format("DELETE FROM InventoryCounts WHERE ProductID = {prodid} AND InventoryID = {invid};", prodid=product_id, invid=inventory_id) + return sql + + def build_sel_code(self, inventory_id: int = None): + if inventory_id is not None: + w = ' WHERE InventoryID = ' + str(inventory_id) + else: + w = '' + sql = str.format("SELECT InventoryID, ProductID, Count " + "FROM InventoryCounts{WHERE};", WHERE=w) + return sql + +class create_tables(): + def create_inventories_tbl(self): + sql = str.format("CREATE TABLE if not exists Inventories (InventoryID int Primary Key, InventoryDate date);") + return sql + + def create_products_tbl(self): + sql = str.format("CREATE TABLE if not exists Products (ProductID int Primary Key, ProductName varchar(100));") + return sql + + def create_inventorycounts_tbl(self): + sql = str.format("CREATE TABLE if not exists InventoryCounts (InventoryID int, ProductID int, Count int, Primary Key (InventoryID,ProductID));") + return sql + +if __name__ == '__main__': + + #Create Tables for Testing + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=create_tables().create_products_tbl()) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=create_tables().create_inventories_tbl()) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=create_tables().create_inventorycounts_tbl()) + + #Insert Data + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=ProductsProcessor().build_ins_code(8888,'MMouse')) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryProcessor().build_ins_code(8888,'2019-01-01')) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryCountsProcessor().build_ins_code(99, 5, 2)) + + #Select Data - all run without errors, but I can only return the cursor object, not the data + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryProcessor().build_sel_code()) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryCountsProcessor().build_sel_code()) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=ProductsProcessor().build_sel_code(5)) + + #Update Data + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryProcessor().build_upd_code(5,'2000-09-09')) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=ProductsProcessor().build_upd_code(5,'thing')) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryCountsProcessor().build_upd_code(100,100,90000)) + + #Delete Data + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryProcessor().build_del_code(5)) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=ProductsProcessor().build_del_code(5)) + DBProcessor().execute_sql_code(db_con=create_connection(), sql_code=InventoryCountsProcessor().build_del_code(2,101)) diff --git a/students/johnwachter/README.txt b/students/johnwachter/README.txt new file mode 100644 index 0000000..9177a8f --- /dev/null +++ b/students/johnwachter/README.txt @@ -0,0 +1 @@ +git status diff --git a/students/johnwachter/dict_set_lab.py b/students/johnwachter/dict_set_lab.py new file mode 100644 index 0000000..c1d9feb --- /dev/null +++ b/students/johnwachter/dict_set_lab.py @@ -0,0 +1,60 @@ +#Dictionaires1 +mydict = {'Name': 'Chris', 'City': 'Seattle', 'Cake':'Chocolate'} +print(mydict) +del mydict['Cake'] +print(mydict) +mydict['Fruit'] = 'Mango' +print(mydict) +print(mydict.keys()) +print(mydict.values()) +print(mydict.get('Cake', 'Cake not in dictionary')) +if 'Mango' in mydict.values(): + print('Mango is in the dictionary') +else: print('Mango not in dictionary') + +#Dictionaries2 - What??? + +#Sets1 +s2 = range(0,21) +s3 = range(0,21) +s4 = range(0,21) + +holds2 = list(s2) +holds3 = list(s3) +holds4 = list(s4) + +l2 = [] +l3 = [] +l4 = [] + +for i in holds2: + if i%2 ==0: + l2.append(i) +for i in holds3: + if i%3 == 0: + l3.append(i) +for i in holds4: + if i%4 == 0: + l4.append(i) + +s2 = set(l2) +s3 = set(l3) +s4 = set(l4) +print(s2, s3, s4) +print(s3 < s2) +print(s4 4} {:<4}|".format(len(donor_db[donor]),"gifts"), "${0:<10,.2f}".format(avg)) + main() + print("+++++++++++++++++++++") + +def quitprogram(): + print("Goodbye") + sys.exit() + +def main(): + while True: + response = input(user_prompt) + if response == "1": + sendthankyou() + elif response == "2": + createreport() + elif response == "3": + quitprogram() + else: + print("Not a valid option!") + +if __name__ == "__main__": + main() diff --git a/students/johnwachter/mailroom/mailroom2.py b/students/johnwachter/mailroom/mailroom2.py new file mode 100644 index 0000000..9c94abf --- /dev/null +++ b/students/johnwachter/mailroom/mailroom2.py @@ -0,0 +1,79 @@ +#Title: MailroomPart2.py +#Change Log: (Who, When, What) +#JWachter, 2019-01-31, Created File +#JWachter. 2019-01-31, Using a dictionary as a case switch instead of If Elif statements + +import sys + +donor_db = {"William Gates, III": [653772.32, 12.17], + "Jeff Bezos": [877.33], + "Paul Allen": [663.23, 43.87, 1.32], + "Mark Zuckerberg": [1663.23, 4300.87, 10432.0], + "Me Myself": [100]} +user_prompt = "\n".join(("Welcome to your Donor Database", "Please choose an option: ", "1 - Create One 'Thank You' Letter", "2 - Create a Report", "3 - Create Thank You letters for all donors", "4 - Quit\n")) + +def sendthankyou(): + user_input = "" + while user_input != "Main Menu": + user_input = input("Let's send some Thank You letters.\nType 'list' to see a list of donors, or input the donors full name to add a gift and send a Thank You letter. To return to the main menu, type 'Main Menu': ") + if user_input in donor_db: + amtdonated = int(input("Please enter the amount donated by {}: ".format(user_input))) + donor_db[user_input].append(amtdonated) + thankyouletterdict = {"Name": user_input, "Amount": amtdonated} + print("+"*45 + "\nNice!\n Thanks, {Name} for the ${Amount}!\n\nSincerely, me\n".format(**thankyouletterdict) + "+"*45) + elif user_input == 'list': + for donor in donor_db: + print(donor + "\n") + elif user_input not in donor_db and user_input != 'Main Menu': + donor_db[user_input] = [] + print("Great, {} has been added to the donor database.".format(user_input)) + amtdonated = int(input("Please enter the amount donated by {}: ".format(user_input))) + donor_db[user_input].append(amtdonated) + thankyouletterdict = {"Name": user_input, "Amount": amtdonated} + print("+" * 45 + "\nNice! Thanks, {Name} for the ${Amount}!\n\nSincerely, me\n".format( + **thankyouletterdict) + "+" * 45) + +def createreport(): + print(" Here is your report\n" + "="*45) + print("Donor Name | Total Given | Num Gifts | Average Gift") + print("--------------------------------------------------------") + l = max(len(donor) for donor in donor_db) + for donor in donor_db: + avg = sum(donor_db[donor])/len(donor_db[donor]) + print("{:20} |".format(donor), "${0:10,.2f}|".format(sum(donor_db[donor])), "{:>4} {:<4}|".format(len(donor_db[donor]),"gifts"), "${0:<10,.2f}".format(avg)) + main() + print("+++++++++++++++++++++") + +# def donorcategory(): +# for donor in donor_db: +# if int(sum(donor_db[donor])) < 10000: +# return 'measly' +# else: +# return 'generous' + +def createthankyouletters(): + for donor in donor_db: + with open('/Users/John/Python210-W19/students/johnwachter/mailroom/{}_letter.txt'.format(donor), 'w') as donorletter: + donorletter.write("+"*45 + "\nNice!\n\nThanks, {} for the ${}! You have given ${} since you started donating.\n\nSincerely, me\n".format(donor, donor_db[donor][-1], sum(donor_db[donor])) + "+"*45) + print("Letters generated.\n") + + +def quitprogram(): + print("Goodbye") + sys.exit() + +def responsedict(choice): + data = {'1': sendthankyou, '2': createreport, '3': createthankyouletters, '4': quitprogram} + return data.get(choice)() + main() + +def main(): + while True: + response = input(user_prompt) + try: + responsedict(response) + except TypeError as te: + print('Please enter 1 2 or 3', type(te)) + +if __name__ == "__main__": + main() diff --git a/students/johnwachter/mailroom/mailroom3.py b/students/johnwachter/mailroom/mailroom3.py new file mode 100644 index 0000000..7d8209e --- /dev/null +++ b/students/johnwachter/mailroom/mailroom3.py @@ -0,0 +1,85 @@ +#tle: MailroomPart3.py +#Change Log: (Who, When, What) +#JWachter, 2019-02-10, Created File +#JWachter, 2019-02-10, put in some error handling +#JWachter, 2019-02-10, looked to use list comprehension, didn't find a good spot + + + +import sys + +donor_db = {"William Gates, III": [653772.32, 12.17], + "Jeff Bezos": [877.33], + "Paul Allen": [663.23, 43.87, 1.32], + "Mark Zuckerberg": [1663.23, 4300.87, 10432.0], + "Me Myself": [100]} +user_prompt = "\n".join(("Welcome to your Donor Database", "Please choose an option: ", "1 - Create One 'Thank You' Letter", "2 - Create a Report", "3 - Create Thank You letters for all donors", "4 - Quit\n")) + +def sendthankyou(): + user_input = "" + while user_input != "Main Menu": + try: + user_input = input("Let's send some Thank You letters.\nType 'list' to see a list of donors, or input the donors full name to add a gift and send a Thank You letter. To return to the main menu, type 'Main Menu': ") + if user_input in donor_db: + amtdonated = int(input("Please enter the amount donated by {}: ".format(user_input))) + donor_db[user_input].append(amtdonated) + thankyouletterdict = {"Name": user_input, "Amount": amtdonated} + print("+"*45 + "\nNice!\n Thanks, {Name} for the ${Amount}!\n\nSincerely, me\n".format(**thankyouletterdict) + "+"*45) + elif user_input == 'list': + for donor in donor_db: + print(donor + "\n") + elif user_input not in donor_db and user_input != 'Main Menu': + donor_db[user_input] = [] + print("Great, {} has been added to the donor database.".format(user_input)) + amtdonated = int(input("Please enter the amount donated by {}: ".format(user_input))) + donor_db[user_input].append(amtdonated) + thankyouletterdict = {"Name": user_input, "Amount": amtdonated} + print("+" * 45 + "\nNice! Thanks, {Name} for the ${Amount}!\n\nSincerely, me\n".format( + **thankyouletterdict) + "+" * 45) + except ValueError as ve: + print('please enter a valid number', type(ve)) +def createreport(): + print(" Here is your report\n" + "="*65) + print("Donor Name | Total Given | Num Gifts | Average Gift") + print("---------------------------------------------------------------") + for donor in donor_db: + try: + avg = sum(donor_db[donor])/len(donor_db[donor]) + print("{:20} |".format(donor), "${0:10,.2f}|".format(sum(donor_db[donor])), "{:>4} {:<4}|".format(len(donor_db[donor]),"gifts"), "${0:<10,.2f}".format(avg)) + except ZeroDivisionError: + print("{} has not given any gifts.".format(donor)) + main() + print("\n") +def donorcategory(): + for donor in donor_db: + if int(sum(donor_db[donor])) < 10000: + return 'measly' + else: + return 'generous' + +def createthankyouletters(): + for donor in donor_db: + with open('/Users/John/Python210-W19/students/johnwachter/mailroom/{}_letter.txt'.format(donor), 'w') as donorletter: + donorletter.write("+"*45 + "\nNice!\n\nThanks, {} for the ${}! You have given ${} since you started donating.\n\nSincerely, me\n".format(donor, donor_db[donor][-1], sum(donor_db[donor])) + "+"*45) + print("Letters generated.\n") + + +def quitprogram(): + print("Goodbye") + sys.exit() + +def responsedict(choice): + data = {'1': sendthankyou, '2': createreport, '3': createthankyouletters, '4': quitprogram} + return data.get(choice)() + main() + +def main(): + while True: + response = input(user_prompt) + try: + responsedict(response) + except TypeError as te: + print('Please enter 1 2 or 3', type(te)) + +if __name__ == "__main__": + main() diff --git a/students/johnwachter/mailroom/mailroom4.py b/students/johnwachter/mailroom/mailroom4.py new file mode 100644 index 0000000..a70a8fb --- /dev/null +++ b/students/johnwachter/mailroom/mailroom4.py @@ -0,0 +1,102 @@ +#Title: MailroomPart4.py +#Change Log: (Who, When, What) +#JWachter, 2019-02-16, Created File +#JWachter, 2019-02-16, Updated create report function to split data processing and data presentation. Now have 2 funcs +#JWachter, 2019-02-16, Added two new helper functions to print an email and add donor info to the database, pulling them out of the send thank you function + +import sys + +donor_db = {"William Gates, III": [653772.32, 12.17], + "Jeff Bezos": [877.33], + "Paul Allen": [663.23, 43.87, 1.32], + "Mark Zuckerberg": [1663.23, 4300.87, 10432.0], + "Me Myself": [100]} +user_prompt = "\n".join(("Welcome to your Donor Database", "Please choose an option: ", "1 - Create One 'Thank You' Letter", "2 - Create a Report", "3 - Create Thank You letters for all donors", "4 - Quit\n")) + +def sendthankyou(database=donor_db): + user_input = "" + while user_input.lower() != "Main Menu": + user_input = input( + "Let's send some Thank You letters.\nType 'list' to see a list of donors, or input the donors full name to add a gift and send a Thank You letter. To return to the main menu, type 'Main Menu': ") + if user_input == "list": + print("\n", donorlist(), "\n") + elif user_input != "Main Menu": + amtdonated = int(input("Please enter the amount donated: ")) + adddonation(user_input, amtdonated, database) + print(tyemail(user_input, amtdonated)) + else: + main() + +def donorlist(): + donorlist = [] + for donor in donor_db: + donorlist.append(donor) + return donorlist + +def adddonation(userinput, donationamount, database=donor_db): + try: + if userinput in database: + database[userinput].append(donationamount) + else: + database[userinput] = [donationamount] + except ValueError as ve: + print('Please enter a valid number', type(ve)) + return database + +def tyemail(user_input, amtdonated): + thankyouletterdict = {"Name": user_input, "Amount": amtdonated} + print("+" * 45 + "\nNice!\n Thanks, {Name} for the ${Amount}!\n\nSincerely, me\n".format( + **thankyouletterdict) + "+" * 45) + +def createreport(database = donor_db): + try: + reportstring = str(" Here is your report\n" + "="*65 + "\n" + "Donor Name | Total Given | Num Gifts | Average Gift\n" + + "---------------------------------------------------------------" + "\n") + for donor in database: + avg = sum(database[donor])/len(database[donor]) + reportstring += "{:20} | ${:10,.2f}| {:>4} {:<4}| ${:<10,.2f} \n".format(donor, sum(database[donor]), len(database[donor]), "gifts", avg) + except ZeroDivisionError as zde: + print("{} has not given any gifts.".format(donor)) + return reportstring + +def displayreport(): + print(createreport(donor_db)) + print("\n") + +def tylettertxt(): + listtxt = {} + for donor in donor_db: + listtxt.update({donor: "+"*45 + "\nNice!\n\nThanks, {} for the ${}! You have given ${} since you started donating.\n\nSincerely, me\n".format(donor, donor_db[donor][-1], sum(donor_db[donor])) + "+"*45}) + return listtxt + +def createthankyouletters(txt = tylettertxt()): + for donor in donor_db: + with open('/Users/John/Python210-W19/students/johnwachter/mailroom/{}_letter.txt'.format(donor), 'w') as donorletter: + donorletter.write(txt) + print("Letters generated.\n") + + +def quitprogram(): + print("Goodbye") + sys.exit() + +def responsedict(choice): + casedict = {'1': sendthankyou, '2': displayreport, '3': createthankyouletters, '4': quitprogram} + if casedict.get(choice) == "1": + return casedict.get(choice) + elif casedict.get(choice) == "3": + return casedict.get(choice(tylettertxt())) + else: + return casedict.get(choice)() + main() + +def main(): + while True: + response = input(user_prompt) + try: + responsedict(response) + except TypeError as te: + print('Please enter 1 2 or 3', type(te)) + +if __name__ == "__main__": + main() diff --git a/students/johnwachter/mailroom/test_mailroom4.py b/students/johnwachter/mailroom/test_mailroom4.py new file mode 100644 index 0000000..eef50c1 --- /dev/null +++ b/students/johnwachter/mailroom/test_mailroom4.py @@ -0,0 +1,58 @@ +from os import path +from mailroom4 import createreport +from mailroom4 import displayreport +from mailroom4 import tylettertxt +from mailroom4 import adddonation +from mailroom4 import donorlist + +test2db = {} +test3db = {} +test4db = {'testdonor': 1} +test5db = {'testdonor': 1, 'testdonor2': 2} + +fullpath1 = r"C:\\Users\\John\\Python210-W19\\students\\johnwachter\\mailroom\\Me Myself_letter.txt" +fullpath2 = r"C:\\Users\\John\\Python210-W19\\students\\johnwachter\\mailroom\\Jeff Bezos_letter.txt" +fullpath3 = r"C:\\Users\\John\\Python210-W19\\students\\johnwachter\\mailroom\\Mark Zuckerberg_letter.txt" +fullpath4 = r"C:\\Users\\John\\Python210-W19\\students\\johnwachter\\mailroom\\Paul Allen_letter.txt" +fullpath5 = r"C:\\Users\\John\\Python210-W19\\students\\johnwachter\\mailroom\\William Gates, III_letter.txt" + +def test_1(): + """Tests if the files that the function is supposed to create actually exist""" + assert path.exists(fullpath1) + print("The file ending with {} does exist | Pass".format(fullpath1[-20:])) + assert path.exists(fullpath2) + print("The file ending with {} does exist | Pass".format(fullpath2[-20:])) + assert path.exists(fullpath3) + print("The file ending with {} does exist | Pass".format(fullpath3[-20:])) + assert path.exists(fullpath4) + print("The file ending with {} does exist | Pass".format(fullpath4[-20:])) + assert path.exists(fullpath5) + print("The file ending with {} does exist | Pass".format(fullpath5[-20:])) + +def test_2(): + """Tests if the function that adds donors and amounts to the database works""" + assert adddonation("Me Myself", "900", database = test2db) == {"Me Myself":['900']} + print("Test_2 passed") + +def test_3(): + """Assert not is used to check if the database is empty""" + assert not donorlist(test3db) + print("Test_3 passed") + +def test_4(): + """Checks if the donorlist function will return a list of donors that is passed to it""" + assert donorlist(test4db) == ['testdonor'] + print("Test_4 passed") + +def test_5(): + assert donorlist(test5db) == ['testdonor', 'testdonor2'] + print("Test_5 passed") + +test_1() +test_2() +test_3() +test_4() +test_5() + + + diff --git a/students/johnwachter/session02/FizzBuzz.py b/students/johnwachter/session02/FizzBuzz.py new file mode 100644 index 0000000..e591a72 --- /dev/null +++ b/students/johnwachter/session02/FizzBuzz.py @@ -0,0 +1,16 @@ +#Title: Lab GridPrinterExercise.py +#Change Log: (Who, When, What) +#JWachter, 2019-01-19, created file and function, fizbuzz, to demonstrate 'if' and 'and' statements + +def fizbuzz(): + """Return values on a list from 1-100 based on the divisibility of 3 and 5""" + for i in range(1,100+1): + if i%3 == 0 and i%5 ==0: + print("FizzBuzz") + elif i %3 == 0: + print("Fizz") + elif i %5 == 0: + print("Buzz") + else: + print(i) +fizbuzz() diff --git a/students/johnwachter/session02/GridPrinterExercise.py b/students/johnwachter/session02/GridPrinterExercise.py new file mode 100644 index 0000000..82c3b13 --- /dev/null +++ b/students/johnwachter/session02/GridPrinterExercise.py @@ -0,0 +1,45 @@ +#Title: Lab GridPrinterExercise.py +#Change Log: (Who, When, What) +#JWachter, 2019-01-19, created file and printed basic grid in part 1 of exercise + +#Data +plusminusrow = '+''-''-''-''-''+''-''-''-''-''+' +piperow = '|'' '' '' '' ''|' ' '' '' '' ''|''\n' +newline = '\n' + +"This is part 1 of the exercise" +print("Part 1") +def printgrid_1(): + """Print a 2x2 grid""" + print(plusminusrow + newline + piperow*4 + plusminusrow + newline + piperow*4 +plusminusrow) +printgrid_1() +"This is part 2 of the exercise" +print("Part 2") +def printgrid_2(n): + """ + Print the visual of a 1x1 grid + :param n: the number of rows in the grid represented by the pipe '|' symbol + :return: Returns True so as to return at least something which follows Pythonic convention. + """ + print(plusminusrow + newline + piperow*n + plusminusrow + newline + piperow*n + plusminusrow) + return True +printgrid_2(1) + + +"This is part 3 of the exercise" +print("Part 3") +def printgrid_3(n_rowsandcolumns = 4, n_sizedunit = 1): + """ + Print the visual of a grid based, whose size is based on arguments fed to parameters in the function. + :param n_rowsandcolumns: Represents the number of times the grid will be repeated across (columns) and down (rows) + :param n_sizedunit: Represents the number of minus '-' symbols that appear across the row of the grid + :return: Returns True so as to return at least something which follows Pythonic convention. + """ + minussymbol = '-' + plusminusrow2 = ('+' + minussymbol * n_sizedunit) + piperow2 = '|' + ' ' * n_sizedunit + grid = (plusminusrow2*n_rowsandcolumns + '+' + newline + piperow2*n_rowsandcolumns + '|' + newline + piperow2 * n_rowsandcolumns + '|' + newline + piperow2 * n_rowsandcolumns + '|' + newline + piperow2 * n_rowsandcolumns + '|' + newline) + print(grid*n_rowsandcolumns, end=plusminusrow2*n_rowsandcolumns +'+') + return True +printgrid_3(4, 9) + diff --git a/students/johnwachter/session02/pythonpushups2.py b/students/johnwachter/session02/pythonpushups2.py new file mode 100644 index 0000000..f7888fd --- /dev/null +++ b/students/johnwachter/session02/pythonpushups2.py @@ -0,0 +1,45 @@ +#Title: pythonpushups2.py +#Change Log: (Who, When, What) +#JWachter, 1/21/2019, continuing python pushups exercises + +"""Given 2 ints, a and b, return True if one if them is 10 or if their sum is 10.""" + +def makes10(a, b): + if a + b == 10: + return True + elif a == 10 or b == 10: + return True + else: + return False + + +"""Given an int n, return True if it is within 10 of 100 or 200. Note: abs(num) computes the absolute value of a number.""" +def near_hundred(n): + if abs(100 - n) <= 10: + return True + elif abs(200 - n) <=10: + return True + else: + return False + +"""Given 2 int values, return True if one is negative and one is positive. Except if the parameter "negative" is True, then return True only if both are negative.""" +def pos_neg(a, b, negative): + if negative == True: + if a < 0 and b < 0: + return True + else: + return False + elif a > 0 and b < 0: + return True + elif a < 0 and b > 0: + return True + else: + return False + +"""Given a string, return a new string where "not " has been added to the front. However, if the string already begins with "not", return the string unchanged.""" + +def not_string(str): + if len(str) >= 3 and str[:3] == "not": + return str + else: + return "not " + str diff --git a/students/johnwachter/session02/series.py b/students/johnwachter/session02/series.py new file mode 100644 index 0000000..f025e7d --- /dev/null +++ b/students/johnwachter/session02/series.py @@ -0,0 +1,86 @@ +#Title: Lab GridPrinterExercise.py +#Change Log: (Who, When, What) +#JWachter, 1/19/2019, creating fibonacci series + +def fibonacci(n): + """Return the nth value in the fibonacci series, starting with zero index""" + if n <= 1: + return n + else: + return fibonacci(n-1)+fibonacci(n-2) + +nterms = 8 +if nterms <= 0: + print("Please enter a positive integer") +else: + print("Fibonacci Sequence: ") + for i in range(nterms): + print(fibonacci(i)) + + +def lucas(n): + """Return the nth value in the Lucas numbers, starting with 2, then 1, then 3, 4, 5....n""" + if n == 0: + return 2 + elif n == 1: + return 1 + else: + return lucas(n - 1) + lucas(n - 2) +iterms = 9 +if iterms <= 0: + print("Please enter a positive integer") +else: + print("Lucas Numbers: ") +for i in range(iterms): + print(lucas(i)) + + +def sumseries(n, n1=0, n2=1): + """ + Return the nth value of a list, based on adding the two previous items in the list to get the next item, in a recursive fashion. + :param n: nth item in the list + :param n1: 0th value in the list + :param n2: 1st value in the list + :return: the value of the nth item in the list + """ + if n == 0: + return n1 + elif n == 1: + return n2 + else: + return sumseries(n - 1, n1, n2) + sumseries(n - 2, n1, n2) + +iterms = 4 +if iterms <= 0: + print("Please enter a positive integer") +else: + print("Sumseries: ") +for i in range(iterms): + print(sumseries(i, 2, 1)) + + +if __name__ == "__main__": + # run tests to ensure funcs above are working properly + assert fibonacci(0) == 0 + assert fibonacci(1) == 1 + assert fibonacci(2) == 1 + assert fibonacci(3) == 2 + assert fibonacci(4) == 3 + assert fibonacci(5) == 5 + assert fibonacci(6) == 8 + assert fibonacci(7) == 13 + + # run tests on lucas function + assert lucas(0) == 2 + assert lucas(1) == 1 + + assert lucas(4) == 7 + + # test if sumseries function with only the necessary argument == ficonacci series, which should be the case + assert sumseries(5) == fibonacci(5) + + # test if sumseries function matched lucas. sumeries is called with all three arguments and matches the values in the lucas function, namely that the zero index value == 2 and the first index value == 1 + assert sumseries(5, 2, 1) == lucas(5) + + print("tests passed") + diff --git a/students/johnwachter/session03/list_lab.py b/students/johnwachter/session03/list_lab.py new file mode 100644 index 0000000..b3d96c3 --- /dev/null +++ b/students/johnwachter/session03/list_lab.py @@ -0,0 +1,52 @@ +#Title: Lab list_lab.py +#Change Log: (Who, When, What) +#JWachter, 2019-01-26, created file to explore the list data structure +#JWachter, 2019-01-28, completed exercises and checkd my work + +#Series 1 +fruit_list = ["Apples", "Pears", "Oranges", "Peaches"] +print(fruit_list) +user_fruit = input("Enter a fruit: ") +fruit_list.append(user_fruit) +print(fruit_list) +user_index = int(input("Give me a number between 1 and {}: ".format(len(fruit_list)))) +fruittodisplay = int(user_index-1) +print("Your number was {} and the fruit is {}".format(user_index, fruit_list[fruittodisplay])) +fruit_list = [input("Enter another fruit: ")] + fruit_list +print(fruit_list) +fruit_list.insert(0, 'Cherry') +print(fruit_list) +for fruit in fruit_list: + if fruit.startswith('P'): + print(fruit) + +#series 2 +print("Series 2: {}".format(fruit_list)) +fruit_list.pop(-1) +print(fruit_list) +user_delete = input("Which fruit would you like to delete? ") +if user_delete in fruit_list: + fruit_list.remove(user_delete) +print(fruit_list) + +#series 3 +reversedfruitlist = fruit_list[::-1] +for fruit in reversedfruitlist: + yes_or_no = input("Do you like {}".format(fruit.lower())) + if yes_or_no == 'no': + fruit_list.remove(fruit) + while yes_or_no != 'yes' and yes_or_no != 'no': + print("Please enter yes or no: ") + yes_or_no = input("Do you like {}".format(fruit.lower())) + if yes_or_no == 'no': + fruit_list.remove(fruit) +print(fruit_list) + +#series4 +fruit_list_reversed = [] +for fruit in fruit_list: + reversefruit = fruit[::-1] + fruit_list_reversed.append(reversefruit) +fruit_list.pop(-1) +print("Original Fruit List with last removed {}".format(fruit_list)) +print("Copy of List, with items reversed {}".format(fruit_list_reversed)) diff --git a/students/johnwachter/session03/mailroom_part1 b/students/johnwachter/session03/mailroom_part1 new file mode 100644 index 0000000..c2fb4f3 --- /dev/null +++ b/students/johnwachter/session03/mailroom_part1 @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/students/johnwachter/session03/slicing_lab b/students/johnwachter/session03/slicing_lab new file mode 100644 index 0000000..e69de29 diff --git a/students/johnwachter/session03/slicing_lab.py b/students/johnwachter/session03/slicing_lab.py new file mode 100644 index 0000000..455b708 --- /dev/null +++ b/students/johnwachter/session03/slicing_lab.py @@ -0,0 +1,70 @@ +#Title: slicing_lab.py +#Change Log: (Who, When, What) +#JWachter, 2019-01-26, Created File and finished exercise + +def swap(sequence): + try: + first = sequence[0], + last = sequence[-1], + mid = sequence[1:-1] + newsequence = last + mid + first + return newsequence + except TypeError: + first = sequence[0] + last = sequence[-1] + mid = sequence[1:-1] + newsequence = last + mid + first + return newsequence +swappedsequence = (0,1,2,3,4) +swapped = swap(swappedsequence) +print("Swap Sequence") +print(swapped) +assert swap("flipped") == "dlippef" +print("Test passed") +print("="*45) + +def remove(tupl): + newtupl = tupl[::2] + return newtupl +mytupl = (1,2,3,4,5,6,7,8,9) +removeditems = remove(mytupl) +print("Remove every other item") +print(removeditems) +assert remove("123456789") == "13579" +asserttupltest = (1, 2, 3, 4) +assert remove(asserttupltest) == (1, 3) +print("Tests passed") +print("="*45) + +def remove4everyother(string): + return string[4:-4:2] +everyother4 = (1,2,3,4,5,'dontshow','show', 'dontshow', 'show', 6,7,8,9) +print("first 4 and the last 4 items removed, and then every other item in the remaining sequence") +print(remove4everyother(everyother4)) +assert remove4everyother("0000123450000") == "135" +print("Test passed") +print('='*45) + +def thirds(string): + thirds = int(len(string)/3) + first = string[0:thirds] + last = string[-thirds:] + mid = string[thirds:-thirds] + return last + first + mid +tuplethirds = (1,1,2,2,3,3) +newthirds = thirds(tuplethirds) +print("last third, then first third, then the middle third in the new order") +print(newthirds) +assert thirds("123") == "312" +assert thirds(tuplethirds) == (3, 3, 1, 1, 2, 2) +print("Tests passed") +print("="*45) + +def reverse(string): + return string[::-1] +reversedstring = reverse("reverse this string") +print("Reverse a string") +print(reversedstring) +assert reverse("racecars") == "sracecar" +print("Test passed") +print("="*45) diff --git a/students/johnwachter/session03/strformat_lab.py b/students/johnwachter/session03/strformat_lab.py new file mode 100644 index 0000000..030c8cb --- /dev/null +++ b/students/johnwachter/session03/strformat_lab.py @@ -0,0 +1,33 @@ +#Title: strformat_lab.py +#Change Log: (Who, When, What) +#JWachter, 1/28/2019, Created File + +#task1 +mytupl = (2, 123.4567, 10000, 12345.67) +print("file_00{0} : {1}, {2:.2e}, {3:.2e}".format(mytupl[0], round(mytupl[1], 2), mytupl[2], mytupl[3])) + +#task2 +# Using your results from Task One, repeat the exercise, but this time using an alternate type of format string (hint: think about alternative ways to use .format() (keywords anyone?), and also consider f-strings if you’ve not used them already). +print("The tuple contains {} items and the values are: {}, {}, {}, {}".format(len(mytupl),*mytupl)) + +#task3 +def arbitrarylengthtupl(): + tupl = (2, 3, 4, 5, 6 ,7) + tupllen = len(tupl) + print("Tuple contains {} items which are ".format(tupllen) + ("{}, "*tupllen).format(*tupl)) +arbitrarylengthtupl() + +#task4 +fiveelementtuple = (4, 30, 2017, 2, 27) +print("0{}, {}, {}, 0{}, {}".format(fiveelementtuple[3], fiveelementtuple[4], fiveelementtuple[2], fiveelementtuple[0], fiveelementtuple[1])) + +#task5 +fruitweightlist = ['oranges', 1.3, 'lemons', 1.1] +twentypercentincrease = 1.2 +print(f"The weight of an {fruitweightlist[0].rstrip('s').upper()} is {fruitweightlist[1]*twentypercentincrease} and the weight of a {fruitweightlist[2].rstrip('s').upper()} is {fruitweightlist[3]*twentypercentincrease}") + +#task6 +data = [["NAME", "AGE", "COST"], ["Greg", "Barnold", "Jarvey"], ['27', '89', '62'], ["$100", "$40,000", "$8,500"]] +col_width = max(len(word) for row in data for word in row) + 2 # padding +for row in data: + print("".join(word.ljust(col_width) for word in row)) diff --git a/students/johnwachter/session04/dict_set_lab.py b/students/johnwachter/session04/dict_set_lab.py new file mode 100644 index 0000000..c1d9feb --- /dev/null +++ b/students/johnwachter/session04/dict_set_lab.py @@ -0,0 +1,60 @@ +#Dictionaires1 +mydict = {'Name': 'Chris', 'City': 'Seattle', 'Cake':'Chocolate'} +print(mydict) +del mydict['Cake'] +print(mydict) +mydict['Fruit'] = 'Mango' +print(mydict) +print(mydict.keys()) +print(mydict.values()) +print(mydict.get('Cake', 'Cake not in dictionary')) +if 'Mango' in mydict.values(): + print('Mango is in the dictionary') +else: print('Mango not in dictionary') + +#Dictionaries2 - What??? + +#Sets1 +s2 = range(0,21) +s3 = range(0,21) +s4 = range(0,21) + +holds2 = list(s2) +holds3 = list(s3) +holds4 = list(s4) + +l2 = [] +l3 = [] +l4 = [] + +for i in holds2: + if i%2 ==0: + l2.append(i) +for i in holds3: + if i%3 == 0: + l3.append(i) +for i in holds4: + if i%4 == 0: + l4.append(i) + +s2 = set(l2) +s3 = set(l3) +s4 = set(l4) +print(s2, s3, s4) +print(s3 < s2) +print(s4 0: + return str[0:3]*n + else: + return False + +#Given a string, return a new string made of every other char starting with the first, so "Hello" yields "Hlo". +def string_bits(str): + return str[0::2] + +#Given a non-empty string like "Code" return a string like "CCoCodCode". +def string_splosion(str): + if str: + counter = 0 + string = "" + while counter <= len(str): + string += str[0:counter] + counter +=1 + return string + +#Given a string, return the count of the number of times that a substring length 2 appears in the string and also as the last 2 chars of the string, so "hixxxhi" yields 1 (we won't count the end substring). +def last2(stri): + if len(stri) < 2: + return 0 + else: + counter = 0 + try: + target = stri[-2:] + for i in range(len(stri) -2): + sub = stri[i:i + 2] + if sub == target: + counter += 1 + except TypeError as te: + return ("error is {}".format(te)) + return counter + + +print(front_times('Chocolate', 2)) +front_times('Chocolate', 3) +front_times('Abc', 3) + +string_bits('Hello') +string_bits('Hi') +string_bits('Heeololeo') + +string_splosion('Code') +string_splosion('abc') +string_splosion('ab') + +last2('hixxhi') +last2('xaxxaxaxx') +last2('axxxaaxx')