A simplified 0/1 Knapsack problem with multiple items allowed in Python - python

I have a problem which is a simplified version of the knapsack problem. It goes like this. There is a store and we have a list of items in that store. Let's say like this,
Store has 2 types of products Normal products and limited products.
Product Class
class Product:
"""
A class to provide abstraction for products availabel in the system.
unless the product is a Limited_product its supply is infinite
"""
product_counter = 0
##### Part 1.1 #####
def __init__(self, name, price):
""" Constructor for initialization """
self.name = name
self.price = price
self.id = Product.product_counter
Product.product_counter += 1
def __str__(self):
""" returns a string representation of a product """
return "<{}> {} - {}$".format(self.id, self.name, self.price)
def __repr__(self):
""" represents the class object as a string """
return "PRODUCT <{}>".format(self.id)
Limited Product Class
class Limited_Product(Product):
"""
A child class of the parent Product class
Represents a Limited product where the quantity is depreceating
"""
##### Part 1.2 #####
def __init__(self, name, price, amount):
""" Constructor for initialization """
super().__init__(name, price)
self.amount = amount
def __str__(self):
""" returns a string representation of a limited product """
return "<{}> {} - {}$ ({} left)".format(self.id, self.name, self.price, self.amount)
def decrement_amount(self):
""" decrement the amount of available product """
self.amount -= 1
def get_amount(self):
""" returns the amount available from the product """
return self.amount
Now we are given a list of products and the maximum amount a customer can spend.
============================
Product - Price
A - 20$
B - 7$
C - 1$ (2 left)
============================
And we have to find out what is the minimum amount left after the customer buys a sequence from these items using both recursion as well as iteratively by completing the given two functions. These functions are given so I cannot write in a different method.
As an example:
>>> store.so_rich(45)
output - 1
You have 45 dollars to spend. The optimal solution is buying six B items and two C items. This will leave you with 45 - 7 * 6 - 1 * 2 = 1 dollars left. The least amount of money left is 1 since there is no way to spend all of the money. (although C’s price is 1$, you have purchased all of them already!)
>>> store.so_rich(61)
0
You have 61 dollars to spend. You can spend all of them by buying two A and three B (20 * 2 + 7 * 3 = 61). So the least amount of money left is 0.
The two functions that I wrote
def so_rich(self, money):
# suppose you haven't seen any product yet
# the only possible amount of money left is "money"
# this is a set to record the possible money left
left = set([money])
# get products
lst = list(self.warehouse.inventory.values())
print(str(lst))
for product in lst:
# a temporary set to save the updates of "left"
# you don't want to modify the set you're iterating through
tmp_left = set()
for m in left:
# update tmp_left
if type(product) != Limited_Product:
new_left = m
#new_left -= product.price
while product.price <= new_left:
print(new_left, product.name)
tmp_left.add(new_left)
new_left = new_left - product.price
else:
# handle limited product
new_left = m
product_count = product.amount
while product.price <= new_left and product_count > 0:
print(new_left, product.name)
tmp_left.add(new_left)
new_left = new_left - product.price
product_count -= 1
left = tmp_left
print(left)
return min(left)
def so_rich_recursive(self, money):
# get products
lst = list(self.warehouse.inventory.values())
def helper(lst, money):
# base case
if not lst:
return money
cur_min = money
product = lst[0]
if type(product) != Limited_Product:
tmp = money
while product.price < tmp:
cur_min = tmp
tmp -= product.price
print(cur_min, product.name)
else:
tmp = money
product_count = product.amount
while product.price <= tmp and product_count > 0:
cur_min = tmp
tmp -= product.price
product_count -= 1
print(cur_min, product.name)
money = money - cur_min
print("-----", money)
lst.pop(0)
return helper(lst, money)
return helper(lst, money)
I cannot understand why the above code written by me does not work. Can anyone help me, please?

Related

How to update values within class

I'm trying to write a class separate from the main class and I want it to keep track of balance and number of items I buy coming from the main class. However, I'm having trouble updating the Num and balance every time I make a transaction and it keeps giving me 0 and 2000 when I print balance and num.
class Wallet:
def __init__(self, Num = 0, balance = 2000):
self.Num = Num
self.balance = balance
def addNum(self, c, d):
if d > self.balance:
return print(f'You have insufficient balance to buy {c}')
else:
self.balance -= d
self.Num += c
return Wallet().print()
def print(self):
print(f'You have {self.Num} of {self.balance}')
Wallet().addNum(3, 30)
Replace Wallet().print() with self.print(), otherwise you are creating a brand new Wallet every time you try to print it.

How to store the total withdrawn amount for each category obejct? [duplicate]

I have a Category class and there is a ledger attribute for each instance of this class. This ledger attribute is actually a list of dictionaries which contain the withdrawal and deposit amounts and descriptions in the form {"amount" : amount, "description" : description}. Now, I want to define a function create_spend_chart which will take a list of objects as the parameter, and will find the total amount of withdrawals. I have been able to do this successfully:
def create_spend_chart(categories):
total_withdrawn = 0
for i in categories:
for p in i.ledger:
if p["amount"] < 0:
total_withdrawn += -p["amount"]
But the problem I'm facing here is, I can't seem to store the total withdrawal amount for each category object separately. How can I do this?
My code-base might help you ins answering the question:
class Category:
def __init__(self, name):
self.name = name
self.ledger = list()
def get_balance(self):
total_balance = 0
for i in self.ledger:
total_balance += i["amount"]
return total_balance
def check_funds(self, amount):
if self.get_balance() >= amount:
return True
else:
return False
def deposit(self, amount, description = "Deposit"):
form = {"amount" : int(amount), "description" : description}
self.ledger.append(form)
def withdraw(self, amount, description = "Withdrawal"):
if description == None:
description = "Withdrawal"
form = {"amount" : -int(amount), "description" : description}
if self.check_funds(amount):
self.ledger.append(form)
return True
else:
return False
def transfer(self, amount, category_object):
form1 = {"amount" : -int(amount), "description" : f"Transfer to {category_object.name}"}
form2 = {"amount" : int(amount), "description" : f"Transfer from {self.name}"}
if self.check_funds(amount):
self.ledger.append(form1)
category_object.ledger.append(form2)
return True
else:
return False
def __repr__(self):
Ledger = ""
for i in self.ledger:
if len(i["description"]) > 23:
des = i["description"][:23]
else:
des = i["description"]
Ledger += des.ljust(23) + str(round(i["amount"], 2)).rjust(7) + "\n"
Ledger = Ledger + "Total: " + str(round(self.get_balance(), 2))
receipt = f"{self.name}".center(30, "*") + "\n" + Ledger
return receipt
def create_spend_chart(categories):
total_withdrawn = 0
withdrawals = list()
for i in categories:
for p in i.ledger:
if p["amount"] < 0:
total_withdrawn += -p["amount"]
PS: This function is not a method, it is defined outside of the class declaration.
Use a collections.defaultdict to make aggregations such as that easy as pie.
import collections
# ...
withdrawn_per_category = collections.defaultdict(int)
for i in categories:
for p in i.ledger:
if p["amount"] < 0:
withdrawn_per_category[i.name] += -p["amount"]
(I've opted to use int as the default data type, but it doesn't truly matter here, so long as it's a conversible numeric type.)
Without collections
If for some reason you don't want to use the handy, built-in collections module, you can emulate the same behavior yourself with a regular dict:
withdrawn_per_category = {}
for i in categories:
for p in i.ledger:
if p["amount"] < 0:
withdrawn_per_category[i.name] = withdrawn_per_category.get(i.name, 0) - p["amount"]

Python Product Inventory

I'm working on a simple product inventory which essentially allows user to add products, display them and deduce the inventory value. I am stuck in the part where the inventory value is deduced. What I want the code to do here is multiply the product price * product quantity for each added product and return the sum. When I run the code, I get the error, TypeError: 'int' object is not iterable. I am new at Python, would appreciate any guidance!
Here is a portion of the code:
class Product:
def __init__(self, idnum, price, quantity):
self.idnum = idnum
self.price = price
self.quantity = quantity
class Inventory:
def __init__(self):
self.productlist = []
def add_product(self):
idnum = int(input('Enter id: '))
price = int(input('Enter price: '))
quantity = int(input('Enter quantity: '))
self.productlist.append(Product(idnum, price, quantity))
return self.productlist
def display(self):
for product in self.productlist:
print(product.idnum, product.price, product.quantity)
def inventory_value(self):
for product in self.productlist:
return sum(product.price*product.quantity)
I think you meant to sum all the products (the result of multiplication) like that:
def inventory_value(self):
running_sum = 0
for product in self.productlist:
running_sum += product.price * product.quantity
return running_sum
Or simply:
def inventory_value(self):
return sum(product.price * product.quantity for product in self.productlist)
Notice that this `sums over a collection, and not a single integer, like in your case:
>>> [x * (x + 1) for x in range(5)]
[0, 2, 6, 12, 20]
>>> sum(x * (x + 1) for x in range(5))
40

Error checking inside a class

I'm supposed to create an Account class which uses some of the functions defined in the class above it. I'm having trouble with error checking in my withdraw class.
def withdraw(self, amount):
if amount > self.money:
return 'Error'
self.money -= amount
>>> a = Money(5,5)
>>> b = Money(10,1)
>>> acct1 = Account('Andrew', a)
>>> print(acct1)
Andrew's account balance is $5.05
>>> c = Money(3,50)
>>> acct1.deposit(c)
>>> print(acct1)
Andrew's account balance is $8.55
>>> acct1.withdraw(b)
>>> print(acct1)
Andrew's account balance is $-2.54
The output should be Error, but instead it just calculates and gives me back a negative balance.
The entire code is here:
class Money:
def __init__(self, dollars = 0, cents = 00):
'constructor'
self.dollars = dollars
self.cents = cents
if self.cents > 99:
self.dollars += 1
self.cents = self.cents - 100
def __repr__(self):
'standard representation'
return 'Money({}, {})'.format(self.dollars,self.cents)
def __str__(self):
'returns a string representation of ($dollars.cents)'
if self.cents < 10:
return '${}.0{}'.format(self.dollars, self.cents)
else:
return '${}.{}'.format(self.dollars, self.cents)
def __add__(self, new):
'Adds two money objects together'
d = self.dollars + new.dollars
c = self.cents + new.cents
return Money(d,c)
def __sub__(self, new):
'Subtracts two money objects'
d = self.dollars - new.dollars
c = self.cents - new.cents
return Money(d,c)
def __gt__(self, new):
'computes greater then calculations'
a = self.dollars + self.cents
b = new.dollars + new.cents
return a > b
class Account:
def __init__(self, holder, money = Money(0,0)):
'constructor'
self.holder = holder
self.money = money
def __str__(self):
return "{}'s account balance is {}".format(self.holder, self.money)
def getBalance(self):
return self.money
def deposit(self, amount):
self.money = self.money + amount
def withdraw(self, amount):
if amount > self.money:
return 'Error'
self.money -= amount
Actually it is because you don't compute the "balance" inside your __gt__ correctly.
The dollars should be multiplied by 100:
def __gt__(self, new):
a = self.dollars * 100 + self.cents
b = new.dollars * 100 + new.cents
return a > b
Optional: Instead of returning 'Error' you should consider raising an Exception:
def withdraw(self, amount):
if amount > self.money:
raise ValueError('Not enough money')
self.money -= amount

Display a list of options that changes based upon conditions, and have user select one of those options

I'm a novice at writing python, and I'm trying to create a dynamic selection of options to the user, ask them what option they want, and then perform a function based upon what they choose.
My "game" is recreating the scene from Die Hard where they have to get 4 gallons into a 5 gallon jug.
The user starts with two bottles that have nothing in them, and water available. Because they are starting out, they should only have two options:
[1] Fill bottle A
[2] Fill bottle B
Select Option:
Assuming the code was right, and chose option 1 and filled bottle A, the next options they have are now:
[1] pour bottle A into bottle B
[2] Fill bottle B
[3] Empty bottle A
Select Option:
Here is my (probably horrible) code thus far:
class Bottles(object):
amount = 0
def __init__(self,name,capacity,amount):
self.name = name
self.capacity = capacity
self.amount = amount
def AddWater(self,increase):
if (self.amount + increase) > self.capacity:
self.amount = self.capacity
print("Overflow! {0}'s at max capacity ({1} gallons)").format(self.name,self.capacity)
else:
self.amount = self.amount + increase
def RemWater(self,decrease):
if (self.amount - decrease) < 0:
self.amount = 0
print("Empty! {0} is now empty!").format(self.name)
else:
self.amount = self.amount - decrease
def ShowOptions():
available_options = []
option_value = 1
print("Bottle A Amount: {0}").format(bottle_a.amount)
print("Bottle B Amount: {0}").format(bottle_b.amount)
print("Your options are as follows:")
if bottle_a.amount != bottle_a.capacity:
print("[{0}] Fill bottle A").format(option_value)
available_options.append(str(option_value))
option_value += 1
if bottle_b.amount != bottle_b.capacity:
print("[{0}] Fill bottle B").format(option_value)
available_options.append(str(option_value))
option_value += 1
if bottle_a.amount != bottle_a.capacity and bottle_b.amount > 0:
print("[{0}] Pour water in Bottle B into Bottle A").format(option_value)
option_value += 1
if bottle_b.amount != bottle_b.capacity and bottle_a.amount != 0:
print("[{0}] Pour water in Bottle A into Bottle B").format(option_value)
option_value += 1
if bottle_a.amount == 4 or bottle_b.amount == 4:
print("{0}] Defuse bomb.").format(option_value)
option_value += 1
bottle_a = Bottles("Bottle A",5,3) # 5 gallon bottle
bottle_b = Bottles("Bottle B",3,0) # 3 gallon bottle
ShowOptions()
What I'm having a hard time grasping is how to both ask for their selection, and then run that function without adding a whole bunch of extra option checking each time.
You could construct a list of pairs where first item is the text to display and second item is a function to execute if option is selected. Then you would have to code the functionality to decide the options only once and executing selected option would be trivial.
Here's an example on how the game could look with above approach:
class Bottle(object):
def __init__(self,name,capacity,amount):
self.name = name
self.capacity = capacity
self.amount = amount
def fill(self):
self.amount = self.capacity
def empty(self):
self.amount = 0
def fill_from(self, bottle):
amount = min(self.capacity - self.amount, bottle.amount)
self.amount += amount
bottle.amount -= amount
# Add all the possible options for given bottle to the list
def add_bottle_options(options, bottle, other):
# Fill
if bottle.amount != bottle.capacity:
options.append(('Fill {}'.format(bottle.name), lambda: bottle.fill()))
# Empty
if bottle.amount:
options.append(('Empty {}'.format(bottle.name), lambda: bottle.empty()))
# Pour to
if bottle.amount != bottle.capacity and other.amount:
text = 'Pour water in {} into {}'.format(other.name, bottle.name)
options.append((text, lambda: bottle.fill_from(other)))
bottle_a = Bottle("Bottle A", 5, 0) # 5 gallon bottle
bottle_b = Bottle("Bottle B", 3, 0) # 3 gallon bottle
ticking = True
def defuse():
global ticking
ticking = False
while ticking:
print('Bottle A Amount: {}, capacity: {}'.format(bottle_a.amount,
bottle_a.capacity))
print('Bottle B Amount: {}, capacity: {}'.format(bottle_b.amount,
bottle_b.capacity))
print("Your options are as follows:")
# List of option text, function to execute tuples
available_options = []
add_bottle_options(available_options, bottle_a, bottle_b)
add_bottle_options(available_options, bottle_b, bottle_a)
if bottle_a.amount == 4 or bottle_b.amount == 4:
available_options.append(('Defuse bomb', defuse))
# Enumerate will return (number, item) tuples where the number starts
# from second parameter. Since items are (name, function)
# tuples themselves enumerate returns (number, (name, function))
# tuples which we need to unpack so that we can use number and name
# for printing. (s, _) does nested unpacking so that name goes to
# variable s and function to _ which is a common name for throwaway data.
for i, (s, _) in enumerate(available_options, 1):
print('[{}] {}'.format(i, s))
try:
# Ask user choice, convert it to int, subtract 1 since displayed
# options use 1-based indexing and execute the selected option
choice = input('Select option: ')
available_options[int(choice) - 1][1]()
except:
print('Invalid option')
print('Congrats, you defused bomb')

Categories