There are many possible solutions, here are some of mine:
import os
from csv import DictReader, DictWriter
# This is some example data, so you can see the format
# and the data structures (list of dicts)
EXAMPLE_FINANCES = [
{"date": "2025-02-17", "amount": "500", "category": "Work Study"},
{"date": "2025-02-17", "amount": "-200", "category": "Senior Gala"},
{"date": "2025-02-18", "amount": "-15.5", "category": "JJ's"},
{"date": "2025-02-18", "amount": "50", "category": "Dining Dollars"},
{"date": "2025-02-19", "amount": "-5", "category": "Joe Coffee"},
{"date": "2025-02-20", "amount": "-50.0", "category": "Music Hum Performance"},
{"date": "2025-02-21", "amount": "-5", "category": "Joe Coffee"},
]
#####################
# UTILITY FUNCTIONS #
#####################
def write_finances_csv(file_path, data):
"""This is a helper function to write the list
of dictionaries in the format above to a file"""
file = open(file_path, 'w')
csvwriter = DictWriter(file, fieldnames=["date", "amount", "category"])
for row in data:
csvwriter.writerow(row)
file.close()
def read_finances_csv(file_path):
"""This is a helper function to read data in the format
above from a file"""
if not os.path.exists(file_path):
return EXAMPLE_FINANCES
file = open(file_path, 'r')
data = []
for row in DictReader(file, fieldnames=["date", "amount", "category"]):
data.append(row)
file.close()
return data
#####################
# COMMAND FUNCTIONS #
#####################
def print_all_entries(data):
"""This function takes a list of dictionaries and prints out
all of the entries with some nice spacing and formatting"""
print("\nAll entries:")
for i, entry in enumerate(data):
date = entry["date"]
amount = entry["amount"]
category = entry["category"]
print(f"\t{i}: {date}: ${amount}\t{category}")
# return data without changes
return data
def reset_to_examples(data):
"""This function ignores `data` and instead
returns the example data from above"""
return EXAMPLE_FINANCES
def clear_entries(data):
"""This data removes all entries from data. We can 'cheat'
and just ignore `data` and return an empty list instead"""
return []
def add_entry(data):
"""This function will prompt the user for date, amount, and category,
and append the new dictionary entry to the end of the list"""
date = input("Enter the date: ")
amount = input("Enter the amount: ")
category = input("Enter the category: ")
data.append({"date": date, "amount": amount, "category": category})
return data
def delete_entry(data):
"""This function will show the entries to the user and then
prompt them for the index of the entry they want to delete"""
"""delete an entry"""
# First print all the entries to remind the user
print_all_entries(data)
# Now ask for an entry
index = int(input("Enter the index of the entry to delete, or -1 to cancel: "))
if index >= 0:
# remove it from the list
data.pop(index)
return data
def calculate_net_total(data):
"""This function will calculate the net total of the data"""
total = 0
for entry in data:
total += float(entry["amount"])
print(f"Net total: {total}")
return data
def calculate_number_of_credits(data):
"""This function will calculate the number of credits in the data"""
credits = 0
for entry in data:
amount = float(entry["amount"])
if amount > 0:
credits += 1
print(f"Number of credits: {credits}")
return data
def calculate_sum_of_credits(data):
"""This function will calculate the sum of credits in the data"""
total_credits = 0
for entry in data:
amount = float(entry["amount"])
if amount > 0:
total_credits += amount
print(f"Sum of credits: {total_credits}")
return data
def calculate_number_of_debits(data):
"""This function will calculate the number of debits in the data"""
debits = 0
for entry in data:
amount = float(entry["amount"])
if amount < 0:
debits += 1
print(f"Number of debits: {debits}")
return data
def calculate_sum_of_debits(data):
"""This function will calculate the sum of debits in the data"""
total_debits = 0
for entry in data:
amount = float(entry["amount"])
if amount < 0:
total_debits += amount
print(f"Sum of debits: {total_debits}")
return data
def calculate_max_credit(data):
"""This function will calculate the maximum credit in the data"""
max_credit = 0
for entry in data:
amount = float(entry["amount"])
if amount > max_credit:
max_credit = amount
print(f"Max credit: {max_credit}")
return data
def calculate_max_debit(data):
"""This function will calculate the maximum debit in the data"""
max_debit = 0
for entry in data:
amount = float(entry["amount"])
if amount < max_debit:
max_debit = amount
print(f"Max debit: {max_debit}")
return data
def calculate_average_credit(data):
"""This function will calculate the average credit in the data"""
total_credits = 0
num_credits = 0
for entry in data:
amount = float(entry["amount"])
if amount > 0:
total_credits += amount
num_credits += 1
if num_credits == 0:
print("Average credit: 0")
else:
print(f"Average credit: {total_credits / num_credits}")
return data
def calculate_average_debit(data):
"""This function will calculate the average debit in the data"""
total_debits = 0
num_debits = 0
for entry in data:
amount = float(entry["amount"])
if amount < 0:
total_debits += amount
num_debits += 1
if num_debits == 0:
print("Average debit: 0")
else:
print(f"Average debit: {total_debits / num_debits}")
return data
def calculate_most_common_credit(data):
"""This function will calculate the most common credit in the data"""
credit_counts = {}
for entry in data:
amount = float(entry["amount"])
if amount > 0:
credit_counts[amount] = credit_counts.get(amount, 0) + 1
if not credit_counts:
print("No credits")
else:
print(f"Most common credit: {max(credit_counts, key=credit_counts.get)}")
return data
def calculate_most_common_debit(data):
"""This function will calculate the most common debit in the data"""
debit_counts = {}
for entry in data:
amount = float(entry["amount"])
if amount < 0:
debit_counts[amount] = debit_counts.get(amount, 0) + 1
if not debit_counts:
print("No debits")
else:
print(f"Most common debit: {max(debit_counts, key=debit_counts.get)}")
return data
def selection_sort_by_key(data, key):
"""This function will sort the data by the key, ascending"""
for i in range(len(data)):
min_index = i
for j in range(i+1, len(data)):
if data[j][key] < data[min_index][key]:
min_index = j
data[i], data[min_index] = data[min_index], data[i]
return data
def sort_entries_by_date(data):
ascending_or_desceding = input("Enter 'asc' for ascending or 'desc' for descending: ")
data = selection_sort_by_key(data, "date")
if ascending_or_desceding == "desc":
data.reverse()
return data
def sort_entries_by_amount(data):
ascending_or_desceding = input("Enter 'asc' for ascending or 'desc' for descending: ")
data = selection_sort_by_key(data, "amount")
if ascending_or_desceding == "desc":
data.reverse()
return data
def sort_entries_by_category(data):
ascending_or_desceding = input("Enter 'asc' for ascending or 'desc' for descending: ")
data = selection_sort_by_key(data, "category")
if ascending_or_desceding == "desc":
data.reverse()
return data
def filter_entries_by_date(data):
"""This function will filter the data by a date"""
date = input("Enter the date to filter by: ")
new_data = []
for entry in data:
if entry["date"] == date:
new_data.append(entry)
return new_data
def filter_entries_by_amount(data):
"""This function will filter the data by an amount"""
amount = input("Enter the amount to filter by: ")
gt_or_lt = input("Enter 'gt' for greater than or 'lt' for less than: ")
new_data = []
for entry in data:
if gt_or_lt == "gt" and float(entry["amount"]) > float(amount):
new_data.append(entry)
elif gt_or_lt == "lt" and float(entry["amount"]) < float(amount):
new_data.append(entry)
return new_data
def filter_entries_by_category(data):
"""This function will filter the data by a category"""
category = input("Enter the category to filter by: ")
new_data = []
for entry in data:
if entry["category"] == category:
new_data.append(entry)
return new_data
def modify_existing_entry(data):
"""This function will show the entries to the user and then
prompt them for the index of the entry they want to modify"""
print_all_entries(data)
index = int(input("Enter the index of the entry to modify, or -1 to cancel: "))
if index >= 0:
date = input("Enter the date: ")
amount = input("Enter the amount: ")
category = input("Enter the category: ")
data[index] = {"date": date, "amount": amount, "category": category}
return data
def remove_duplicate_entries(data):
"""This function will remove duplicate entries from the data"""
seen = set()
new_data = []
for entry in data:
key = (entry["date"], entry["amount"], entry["category"])
if key not in seen:
seen.add(key)
new_data.append(entry)
return new_data
def remove_invalid_entries(data):
"""This function will remove invalid entries from the data"""
new_data = []
for entry in data:
date = entry["date"]
amount = entry["amount"]
category = entry["category"]
try:
float(amount)
except ValueError:
continue
if not date or not category:
continue
new_data.append(entry)
return new_data
#################
# MAIN FUNCTION #
#################
def main():
"""This is our main function. It is the primary entry point into our program
and it does a couple of things. See the inline comments for details"""
# Our list of commands.
# This is a dictionary mapping a command (in our case an integer)
# to the description of the command and the function. All of our
# commands take the the form:
#
# new_data = function(current_data)
#
# So they will take the current list of expenses and return a (maybe different)
# list of expenses.
#
# You will add new commands here as you implement them
commands = {
"0": ["Save and quit", None],
"1": ["Print all entries", print_all_entries],
"2": ["Reset to examples", reset_to_examples],
"3": ["Clear all entries", clear_entries],
"4": ["Add new entry", add_entry],
"5": ["Delete an entry", delete_entry],
# YOUR NEW COMMANDS HERE
"6": ["Calculate number of credits", calculate_number_of_credits],
"7": ["Calculate sum of credits", calculate_sum_of_credits],
"8": ["Calculate number of debits", calculate_number_of_debits],
"9": ["Calculate sum of debits", calculate_sum_of_debits],
"10": ["Calculate max credit", calculate_max_credit],
"11": ["Calculate max debit", calculate_max_debit],
"12": ["Calculate average credit", calculate_average_credit],
"13": ["Calculate average debit", calculate_average_debit],
"14": ["Calculate most common credit", calculate_most_common_credit],
"15": ["Calculate most common debit", calculate_most_common_debit],
"16": ["Sort entries by date", sort_entries_by_date],
"17": ["Sort entries by amount", sort_entries_by_amount],
"18": ["Sort entries by category", sort_entries_by_category],
"19": ["Filter entries by date", filter_entries_by_date],
"20": ["Filter entries by amount", filter_entries_by_amount],
"21": ["Filter entries by category", filter_entries_by_category],
"22": ["Modify existing entry", modify_existing_entry],
"23": ["Remove duplicate entries", remove_duplicate_entries],
"24": ["Remove invalid entries", remove_invalid_entries],
"25": ["Calculate net total", calculate_net_total],
}
# This is the file we will work from
finances_csv_filename = "finances.csv"
# Load up the data
data = read_finances_csv(finances_csv_filename)
# Print a welcome message
print("Welcome to the finances app!")
# Enter an infinite loop until the user
# chooses to quit
while True:
# Print the available commands
print("\nPick a command to continue:")
for command_number in commands:
description, function = commands[command_number]
print(f"\t{command_number}. {description}")
# Get the user's choice
choice = input("Enter a command number: ")
# If the choice is not valid, print an error message
if choice not in commands:
print("Invalid choice. Please try again.")
continue
# Handle saving and quitting
if choice == "0":
break
# Otherwise, call the function associated with the choice
# Pass in the data and return the new/modified data
data = commands[choice][1](data)
# Finally, save the changes
write_finances_csv(finances_csv_filename, data)
if __name__ == "__main__":
main()