How to make a simple family budget calculator - python

I am making a simple budget calculator and It is a little bit complicated for me as a beginner. I need to add elements to a list through a function and then print an overview of a specific month and a year. It should sum up the money in each category and print it out.
budget=[]
def add_element (day,month,year,money,category):
budget.append(day,month,year,money,category)
def overview (month,year):
add_element(15,10,2022,150,"food")
add_element(16,11,2022,250,"food")
add_element(17,11,2022,300,"living")
add_element(18,11,2022,500,"food")
add_element(19,11,2022,150,"household")
print(overview(11,2022))
I am expecting this outcome:
{"food": 750, "household": 150, "living": 300}

Similar with using defaultdict
from collections import defaultdict
budget = []
def add_element(*row):
budget.append(row)
def overview(month, year):
summary = defaultdict(int)
for d, m, y, money, category in budget:
if m == month and y == year:
summary[category] += money
return summary
add_element(15, 10, 2022, 150, "food")
add_element(16, 11, 2022, 250, "food")
add_element(17, 11, 2022, 300, "living")
add_element(18, 11, 2022, 500, "food")
add_element(19, 11, 2022, 150, "household")
print(overview(11, 2022)) # defaultdict(<class 'int'>, {"food": 750, "household": 150, "living": 300})
you can anytime convert to dict using dict(summary)

The below code should help you with your need.
def add_element(day, month, year, money, category):
budget.append([day, month, year, money, category])
def overview(month, year):
food_total = 0
household_total = 0
living_total = 0
for item in budget:
if item[1] == month and item[2] == year:
if item[4] == "food":
food_total += item[3]
elif item[4] == "household":
household_total += item[3]
elif item[4] == "living":
living_total += item[3]
return {"food": food_total, "household": household_total, "living": living_total}
budget = []
add_element(15,10,2022,150,"food")
add_element(16,11,2022,250,"food")
add_element(17,11,2022,300,"living")
add_element(18,11,2022,500,"food")
add_element(19,11,2022,150,"household")
print(overview(11,2022))

Related

Including another object inside an object and calling a specific value

I'm trying to make a simple 5v5 sport management, but I'm not sure how can I link players to teams, in order to make the overall rating of the team be the sum of the players rating / amount of players. Unfortunately my current code only returns where the object is ( <main.Player object at 0x000001CED897FCD0>) but not the player at all, so of course the overall is off
class Player:
totalPlayers = 0
def __init__(self,PName, PYearBorn, POvr, PNumber, PIndex):
self.PName = PName
self.PYearBorn = PYearBorn
self.POvr = POvr
self.PNumber = PNumber
self.PIndex = PIndex
Player.totalPlayers += 1
def getPlayer(self):
return self.PName, self.PYearBorn, self.POvr, self.PNumber, self.PIndex
'''
Player Name
Year Born
Player Overall
Player Number
Player Index'''
class HistoricPlayer(Player):
def __init__(self,PName, PlayerAge, POvr, PNumber, PIndex):
self.PlayerAge=PlayerAge
Player.__init__(self,PName, PlayerAge, POvr, PNumber, PIndex,)
class Franchise():
totalFranchises = 0
def __init__(self, FName, region, championships, yearCreated, rivals):
self.FName = FName
self.region = region
self.championships = championships
self.yearCreated = yearCreated
self.rivals = rivals
Franchise.totalFranchises += 1
class Team(Franchise):
totalTeams = 0
def __init__(self, TName, FName, amountofplayers, maxplayercapacity, region, championships, yearCreated, rivals, currentplayers):
Franchise.__init__(self, FName, region, championships, yearCreated, currentplayers)
self.currentplayers = currentplayers
self.overall = currentplayers[1].POvr/amountofplayers
self.rivals = rivals
self.TName = TName
self.amountofplayers = amountofplayers
self.maxplayercapacity = maxplayercapacity
self.currentplayers = currentplayers
Team.totalTeams+=1
def getTeam(self):
return self.TName, self.FName, self.amountofplayers, self.maxplayercapacity, self.region, self.championships, self.yearCreated, self.rivals, self.currentplayers
''' #Team Class Values
Team Name
Franchise Name
Overall
Amount of current players
Maximum player capacity
Team Region
Championships
Year of Creation
Rivals
Current Players
'''
P01=Player('Francis', 2000, 8, 69, 'p01')
P02=Player('Franton', 2000, 8, 69, 'p01')
P03=Player('Frank', 2000, 8, 69, 'p01')
P04=Player('Felitio', 2000, 8, 69, 'p01')
P05=Player('Fred', 2000, 8, 69, 'p01')
T01=Team('5 Friends', "The friends' club", 5, 6, "Hometown", 0, 2022, 'Rich', (P01, P02, P03, P04, P05))
print(T01.getTeam())
Any idea what should or can I do/what am I doing wrong?

Speed up nested for loops in Python

I have a function as below & the inputs are given as:
Inputs:
items = ['17','2','8','34']
item_mapping = {'17':['17','5','8'],'2':['2','0'],'8':['8','2','90'],'34':['34','33']}
item_history = {'India': {'17': '2021-11-14',
'2': '2021-11-10',
'8': '2021-11-8',
'34': '2021-09-22',
'90': '2021-11-5',
'33': '2021-11-11',
'56': '2021-09-22',
'0': '2021-11-3',
'5': '2021-11-8']},
'USA': {'17': '2021-11-10',
'2': '2021-11-20',
'8': '2021-11-25',
'34': '2021-09-22',
'90': '2021-11-6',
'33': '2021-11-30',
'56': '2021-09-22',
'0': '2021-11-1',
'5': '2021-11-13']
}
The function:
def get_results(items,item_mapping,item_history):
result_list = []
for item in items:
current_line = [item]
if item in item_mapping:
identical_item_list = item_mapping[item]
else:
identical_item_list = [item]
for area, history in item_history.items():
item_history_list = []
for identical_item in identical_item_list:
if identical_item in items and identical_item in history:
item_history_list.append([identical_item, history[identical_item]])
if len(item_history_list) > 0:
sorted_item_history_list = sorted(item_history_list, key=lambda x: x[1], reverse=True)
selected_item = sorted_item_history_list[0][0]
current_line += [area, selected_item]
else:
pass
master_item_to_item_list.append(" ".join(current_line))
return result_list
For each item in list - 'items', I have the list of items identical to it in the dictionary -'item_mapping'.
I also have a dictionary - 'item_history' which has the dates on which items were sold in different regions.
The objective I am trying to achieve is, for each item, I would like to get the latest sold similar item in each region.
For example, take item 17:
The similar items are ['17','5','8']
For region 'India', the latest sold similar item, for item 17 is 17.
For region 'USA', it is '8'.
Hence the output line will be : ['17' 'India' '17' 'USA' '8']
Similarly, I want to get for all items and write lines to a text file.
When I have hundreds of thousands of items, it takes hours to do this multiple for loops with sorting and searches. How can I optimize this code? Any other data structures I can use?
I tried implementing multiprocessing like below: but it's still equally slow.
def get_results(item,item_mapping,item_history):
result_list = []
current_line = [item]
if item in item_mapping:
identical_item_list = item_mapping[item]
else:
identical_item_list = [item]
for area, history in item_history.items():
item_history_list = []
for identical_item in identical_item_list:
if identical_item in items and identical_item in history:
item_history_list.append([identical_item, history[identical_item]])
if len(item_history_list) > 0:
sorted_item_history_list = sorted(item_history_list, key=lambda x: x[1], reverse=True)
selected_item = sorted_item_history_list[0][0]
current_line += [area, selected_item]
else:
pass
master_item_to_item_list.append(" ".join(current_line))
return result_list
import multiprocessing as mp
import functools
with mp.Pool(28) as pool:
result = pool.map(functools.partial(get_results,item_mapping=item_mapping,item_history=item_history), items)

Python return list of items that fit set parameters

I'm having some difficulty properly boxing the returned solution to the min_cals <= sum(calories) <= max_cals. There are combinations that yield solutions where sum(cals) < min_cals. In addition to remaining within the caloric range the solution should yield a list where the sum(cost approaches as closely to budget as possible without exceeding the budget limit. Here's some re-contextualized code. I could really use a hand:
menu = [
{'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
{'name':'House Salad', 'calories': 100, 'cost': 8.5},
{'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
{'name':'Beef Brisket', 'calories': 400, 'cost': 12},
{'name':'Soda', 'calories': 100, 'cost': 1},
{'name':'Cake', 'calories': 300, 'cost': 3},
]
def menu_recommendation(menu, min_cal, max_cal, budget):
menu = [item for item in menu if item['calories'] <= max_cal and item['cost'] <= budget]
if len(menu) == 0: return []
return min((
[item] + menu_recommendation(menu, min_cal - item['calories'], max_cal - item['calories'], budget - item['cost'])
for item in menu
), key=
lambda recommendations: [budget - sum(item['cost'] for item in recommendations) and min_cal <= sum(item['calories'] for item in recommendations) <= max_cal, -sum(item['calories'] for item in recommendations)]
)
recommendation = menu_recommendation(menu, 1000, 1200, 15)
total_cost = sum(item['cost'] for item in recommendation)
total_cals = sum(item['calories'] for item in recommendation)
print(f'recommendation: {recommendation}')
print(f'total cost: {total_cost}')
print(f'total calories: {total_cals}')
for example, the following returns a solution with a total calorie count of 700, which is below the 1000 minimum.
recommendation = menu_recommendation(menu, 1000, 1200, 15)
We can work up something recursive, probably.
def smallest_combo(lst, m, n, z):
# filter list to remove elements we can't use next without breaking the rules
lst = [dct for dct in lst if m <= dct['x'] <= n and dct['y'] <= z]
# recursive base case
if len(lst) == 0:
return []
# go through our list of eligibles
# simulate 'what would the best possibility be if we picked that one to go with next'
# then of those results select the one with the sum('y') closest to z
# (breaking ties with the largest sum('x'))
return min((
[dct] + smallest_combo(lst, m - dct['x'], n - dct['x'], z - dct['y'])
for dct in lst
), key=
lambda com: [z - sum(d['y'] for d in com), -sum(d['x'] for d in com)]
)
inp = [{'name': 'item1', 'x': 600, 'y': 5},
{'name': 'item2', 'x': 200, 'y': 8},
{'name': 'item3', 'x': 500, 'y': 12.5},
{'name': 'item4', 'x': 0, 'y': 1.5},
{'name': 'item5', 'x': 100, 'y': 1}]
print(smallest_combo(inp, 500, 1500, 25))
# [{'name': 'item3', 'x': 500, 'y': 12.5}, {'name': 'item3', 'x': 500, 'y': 12.5}]
There would be a number of ways to speed this up. First by making a recursive cache, and second by taking a dynamic programming approach instead (i.e. start at the bottom instead of at the top).
Here is a dynamic programming solution that builds up a data structure showing all of the (calorie, cost) options we can wind up with along with one item each. We look for the best one meeting the criteria, then find what recommendation that is.
def menu_recommendation(menu, min_cal, max_cal, budget):
# This finds the best possible solution in pseudo-polynomial time.
recommendation_tree = {(0, 0.0): None}
for item in menu:
# This tree will wind up being the old plus new entries from adding this item.
new_recommendation_tree = {}
for key in recommendation_tree.keys():
calories, cost = key
new_recommendation_tree[key] = recommendation_tree[key]
new_key = (calories + item['calories'], cost + item['cost'])
if new_key not in recommendation_tree and new_key[0] <= max_cal:
# This is a new calorie/cost combination to look at.
new_recommendation_tree[new_key] = item
# And now save the work.
recommendation_tree = new_recommendation_tree
# Now we look for the best combination.
best = None
for key in recommendation_tree:
calories, cost = key
# By construction, we know that calories <= max_cal
if min_cal <= calories:
if best is None or abs(budget - cost) < abs(budget - best[1]):
# We improved!
best = key
if best is None:
return None
else:
# We need to follow the tree back to the root to find the recommendation
calories, cost = best
item = recommendation_tree[best]
answer = []
while item is not None:
# This item is part of the menu.
answer.append(item)
# And now find where we were before adding this item.
calories = calories - item['calories']
cost = cost - item['cost']
best = (calories, cost)
item = recommendation_tree[best]
return answer
I came up with this, it's basically a knapsack but it removes recursively dishes from the menu if they are not suitable for the recommendation:
menu = [
{'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
{'name':'House Salad', 'calories': 100, 'cost': 8.5},
{'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
{'name':'Beef Brisket', 'calories': 400, 'cost': 12},
{'name':'Soda', 'calories': 100, 'cost': 1},
{'name':'Cake', 'calories': 300, 'cost': 3},
]
def get_price(recommendation):
return sum(dish["cost"] for dish in recommendation)
def get_calories(recommendation):
return sum(dish["calories"] for dish in recommendation)
def menu_recommendation(menu, min_cal, max_cal, budget):
sorted_menu = sorted(menu, key=lambda dish: dish["cost"], reverse=True)
recommendation = []
for dish in sorted_menu:
if dish["cost"] + get_price(recommendation) <= budget:
recommendation.append(dish)
if recommendation:
if get_calories(recommendation) < min_cal:
sorted_menu.remove(min(recommendation, key=lambda dish: dish["calories"]/dish["cost"]))
return menu_recommendation(sorted_menu, min_cal, max_cal, budget)
if get_calories(recommendation) > max_cal:
sorted_menu.remove(max(recommendation, key=lambda dish: dish["calories"]/dish["cost"]))
return menu_recommendation(sorted_menu, min_cal, max_cal, budget)
return recommendation
recommendation = menu_recommendation(menu, 500, 800, 15)
total_cost = sum(item['cost'] for item in recommendation)
total_cals = sum(item['calories'] for item in recommendation)
print(f'recommendation: {recommendation}')
print(f'total cost: {total_cost}')
It removes elements according to the calorie/cost rate, because it's the cost to which is applied the knapsack.
Please let me know if you have any question.
I believe I have solved the issue of making recommendations that were outside of the scoped min_cal / max_cal boundaries, but I still feel like there could be a solution that more closely approaches budget.
Here is my updated code:
menu = [
{'name':'Cheese Pizza Slice', 'calories': 700, 'cost': 4},
{'name':'House Salad', 'calories': 100, 'cost': 8.5},
{'name':'Grilled Shrimp', 'calories': 400, 'cost': 15},
{'name':'Beef Brisket', 'calories': 400, 'cost': 12},
{'name':'Soda', 'calories': 100, 'cost': 1},
{'name':'Cake', 'calories': 300, 'cost': 3},
]
def menu_recommendation(menu, min_cal, max_cal, budget):
menu = [item for item in menu if item['calories'] <= max_cal and item['cost'] <= budget]
if len(menu) == 0: return []
return min(([item] + menu_recommendation(menu, min_cal - item['calories'], max_cal - item['calories'], budget - item['cost'])
for item in menu
), key=
lambda recommendations: [budget - sum(item['cost'] for item in recommendations)
and min_cal - sum(item['calories'] for item in recommendations) >= 0
and max_cal - sum(item['calories'] for item in recommendations) >= 0,
-sum(item['calories'] for item in recommendations)]
)
recommendation = menu_recommendation(menu, 1000, 1200, 15)
total_cost = sum(item['cost'] for item in recommendation)
total_cals = sum(item['calories'] for item in recommendation)
print(f'recommendation: {recommendation}')
print(f'total cost: {total_cost}')
print(f'total calories: {total_cals}')
If anyone has any improvements I'd love to hear them!

How to append to a list using another list of namedtuples?

so I'm very new to Python and got stuck on a problem for my introductory CS class. The problem is to create a a list containing all titles created before 2000 and all titles created after 2000. This is what I have so far:
from collections import namedtuple
Book = namedtuple("Book", "author title genre year price instock")
book_1 = Book("Bob", "Harry Potter", "Fantasy", 2000, 6.00, 1000)
book_2 = Book("Martha", "Hunger Games", "Psychological", 1998, 10.00, 2000)
book_3 = Book("Sam", "The Quest", "Adventure", 2010, 8.00, 5000)
book_4 = Book("Damien", "Pokemon", "Sci-Fi", 1990, 12.00, 10000)
book_5 = Book("Voldemort", "Maze Runner", "Adventure", 2015, 10.00, 50)
book_6 = Book("Anonymous", "Horror Stories Before Bed", "Horror", 2017, 18.00,0)
book_store_inventory = [book_1, book_2, book_3, book_4, book_5, book_6]
before_2000 = []
after_2000 = []
for i in book_store_inventory:
if book_store_inventory[i].year <= 2000:
before_2000.append(i.title)
else:
after_2000.append(i.title)
What should I change around from this point? I keep getting error messages saying list indices must be integers or slices, not Book. Thanks!
you don't need an index:
for book in book_store_inventory:
if book.year <= 2000:
before_2000.append(book.title)
else:
after_2000.append(book.title)
before_2000 = [i.title for i in book_store_inventory if i.year <= 2000]
after_2000 = [i.title for i in book_store_inventory if i.year > 2000]
Since you have so many book objects, it may make sense to create a new class to store the books and create property decorators to access the library data based on the certain condition:
class Library:
def __init__(self, books):
self.books = books
#property
def before_2000(self):
return [i for i in self.books if i.year <= 2000]
#property
def after_2000(self):
return [i for i in self.books if i.year > 2000]
def __repr__(self):
return '{}({})'.format(self.__class__.__name__, ', '.join(i.title for i in self.books))
book_store_inventory = [book_1, book_2, book_3, book_4, book_5, book_6]
library = Library(book_store_inventory)
print(library.before_2000)
print(library.after_2000)
print(library)
Output:
[Book(author='Bob', title='Harry Potter', genre='Fantasy', year=2000, price=6.0, instock=1000), Book(author='Martha', title='Hunger Games', genre='Psychological', year=1998, price=10.0, instock=2000), Book(author='Damien', title='Pokemon', genre='Sci-Fi', year=1990, price=12.0, instock=10000)]
[Book(author='Sam', title='The Quest', genre='Adventure', year=2010, price=8.0, instock=5000), Book(author='Voldemort', title='Maze Runner', genre='Adventure', year=2015, price=10.0, instock=50), Book(author='Anonymous', title='Horror Stories Before Bed', genre='Horror', year=2017, price=18.0, instock=0)]
Library(Harry Potter, Hunger Games, The Quest, Pokemon, Maze Runner, Horror Stories Before Bed)

Python: Replace value with the value from a dictionary key value pair

I have been racking my brain on this for hours now. I'm trying to replace the offense number which is 1-30 to its corresponding offense type i.e. stealing, embezzlement, Burglary, etc. and then sort that into a list.
Here is a sample of the output I currently have:
offense # : Victim Total
1 189
10 712
11 1844
12 184
13 147
14 4364
15 595
16 175
17 387
18 2893
2 597
20 661
Here is what code I have thus far. The offense_map dictionary is what I would like to use to replace the 1-30 in the output to the offense type. Then sort the list in descending order from the largest victim count (right column) to the least. I am working with ~100,000 rows of data so efficiency is important for this program.
from collections import Counter
incidents_f = open('incidents.csv', mode = "r")
crime_dict = dict()
for line in incidents_f:
line_1st = line.strip().split(",")
if line_1st[0].upper() != "REPORT_NO":
report_no = line_1st[0]
offense = line_1st[3]
zip_code = line_1st[4]
if len(zip_code) < 5:
zip_code = "99999"
if report_no in crime_dict:
crime_dict[report_no].append(zip_code).append(offense)
else:
crime_dict[report_no] = [zip_code]+[offense]
#close File
incidents_f.close
details_f = open('details.csv',mode = 'r')
for line in details_f:
line_1st = line.strip().split(",")
if line_1st[0].upper() != "REPORT_NO":
report_no = line_1st[0]
involvement = line_1st[1]
if involvement.upper() != 'VIC':
continue
else:
crime_dict[report_no].append(involvement.upper())
#close File
details_f.close
offense_map = {'1':'Homicide','2':'Rape','3':'Robbery','4':'Assault','5':'Burglary','6':'Stealing','7':'Auto Theft','8':'Non Agg Assault','9':'Arson','10':'Forgery','11':'Fraud','12':'Embezzlement','13':'Stolen Property','14':'Property Damage','15':'Weapons Law Violation','16':'Prostitution','17':'Sex Offense Other','18':'Possession/Sale/Dist','20':'Family Offense','21':'DUI','22':'Liquor Law Violation','24':'Disorderly','25':'Loitering','26':'Misc Violation','29':'Missing/Runaway','30':'Casualty/Suicide'}
victims_by_offense = {}
for k, v in crime_dict.items():
zip = v[1]
if zip not in victims_by_offense.keys():
victims_by_offense[zip] = 0
victims_by_offense[zip] += v[0:].count('VIC')
for zip in sorted(victims_by_offense.keys()):
print(zip, victims_by_offense[zip])
To get a list of keys in victims_by_offense in descending order of Victim Total:
victims_by_offense = {'1': 189, '10': 712, '11': 1844, '12': 184, '13': 147, '14': 4364, '15': 595, '16': 175, '17': 387, '18': 2893, '2': 597, '20': 661}
sorted_keys = sorted(victims_by_offense, key=victims_by_offense.get, reverse=True)
Then
for zip in sorted_keys:
print(offense_map[zip], victims_by_offense[zip])
I get
('Property Damage', 4364)
('Possession/Sale/Dist', 2893)
('Fraud', 1844)
('Forgery', 712)
('Family Offense', 661)
('Rape', 597)
('Weapons Law Violation', 595)
('Sex Offense Other', 387)
('Homicide', 189)
('Embezzlement', 184)
('Prostitution', 175)
('Stolen Property', 147)
('Homicide', 189)
('Embezzlement', 184)
('Prostitution', 175)
('Stolen Property', 147)
I tweaked your code a bit to use csv.reader objects instead of stripping and splitting yourself, as well as changed your data structure to be
crimes = {report_no: {'offense': offense_number,
'zip': zip_code,
'victims': victim_count},
...}
but I think it works much better this way.
import csv
import itemgetter
crimes = dict()
# build `crimes` dict with zero-count victims
with open("incidents.csv") as f:
reader = csv.reader(f)
headers = next(reader)
for report_no, _, _, offense, zip_code, *_ in reader:
if len(zip_code) < 5:
zip_code = "99999"
report = (zip_code, offense)
crimes[report_no] = {'offense': offense,
'zip': zip_code,
'victims': 0}
# parse victims information
with open("details.csv") as f:
reader = csv.reader(f)
headers = next(reader)
for report_no, involvement, *_ in reader:
if involvement.upper() == "VIC":
crimes[report_no]['victims'] += 1
offense_map = {'1':'Homicide',
'2':'Rape',
'3':'Robbery',
'4':'Assault',
'5':'Burglary',
'6':'Stealing',
'7':'Auto Theft',
'8':'Non Agg Assault',
'9':'Arson',
'10':'Forgery',
'11':'Fraud',
'12':'Embezzlement',
'13':'Stolen Property',
'14':'Property Damage',
'15':'Weapons Law Violation',
'16':'Prostitution',
'17':'Sex Offense Other',
'18':'Possession/Sale/Dist',
'20':'Family Offense',
'21':'DUI',
'22':'Liquor Law Violation',
'24':'Disorderly',
'25':'Loitering',
'26':'Misc Violation',
'29':'Missing/Runaway',
'30':'Casualty/Suicide'}
counts = {k: 0 for k in offense_map.values()}
# start counting crimes by victim count (by name, not number)
for crime_info in crimes.values()
try:
offense_no = crime_info['offense']
offense_name = offense_map[offense_no]
counts[offense_name] += crime_info['victims']
except KeyError:
# we couldn't map that
print("No such offense: {}".format(crime_info['offense']))
# sort by value
for k,v in sorted(counts.items(), key=operator.itemgetter(1), reverse=True):
print(k, v)

Categories

Resources