Python: recursive way to subtract from class attributes? - python

I have a class with many attributes, and when I give a number, I would like it to subtract that from one attribute, but if the amount is greater than the attribute subtracted from, move to the next attribute with what is left over. Example:
def subt(self, amount):
self.attr1 += amount
if self.attr1 < 0:
self.attr2 += self.attr1
self.attr1 = 0
if self.attr2 < 0:
# etc...
It feel like there should be a concise recursive way to accomplish the same thing, but I don't know how with the all the different attributes.

You can access the attributes using .__dict__ You need a list for the order you want to subtract. Something like this works.
class A():
def __init__(self):
self.foo = 100
self.bar = 200
self.baz = 300
self.sub_stack = ['foo', 'baz', 'bar']
def subt(self, amount):
tmp_stack = self.sub_stack[:]
while tmp_stack and amount:
key = tmp_stack.pop(0)
val = self.__dict__[key]
if val > amount:
self.__dict__[key] -= amount
amount = 0
else:
amount -= self.__dict__[key]
self.__dict__[key]=0
return amount
return value is the remainder on amount after iterating through your attributes

Does making a list out of all the attributes work?
def subt(self, amount):
i = 0
while i<self.attrlist.len:
if attrlist[i] < amount:
attrlist[i] -= amount
break

Yes, the best way is to create a list of all your attributes - do it either manually,
or, if your attribute names follow a pattern (like the attrN series on the example),
you can automate the creation of such a list.
def subt(self, amount):
#all_attrs = ["attr1", "attr2", "attr3"]
# or this step can be automated by something like:
all_attrs = sorted(attr_name for attr_name in self.__class__.__dict__.keys() if attr_name.startswith("attr"))
for i, attr_name in all_attrs:
self.__setattr__(getattr(self, attr_name) + amount)
if gettattr(self, attr_name) < 0:
amount = gettattr(self, attr_name)
self.__setattr__(self, attr_name, 0)
else:
break

Related

Python: FIFO with limited depth - optimisation question and review

Again, I am new to python. I need a FIFO with a limited depth.
F.e the depth is 5000, so after 5000 and more added items the first one's should be deleted to keep its depth is 5000. Some times I need to read 'the first' one and sometimes read the 'last one'. If I read the first one then it should be removed.
# class
class DictionaryDeque:
from collections import OrderedDict
def __init__(self, dequeDict=10):
self._stack = OrderedDict()
self._range = dictRange
self.setRange(dictRange)
self._len = 0
def len(self):
self._len = len(self._stack)
return self._len
def getRange(self):
return self._range
def setRange(self, range):
self._range = range
# change the dict range if the dict has more items
self.do_pop()
def add(self, key, value):
self._stack[key] = value
self.len()
self.do_pop()
def stack(self):
if self._len > 0:
self.do_pop()
return self._stack
else:
return ""
def last(self):
self.do_pop()
if self._len > 0:
return list(self._stack)[-1]
else:
return list(self._stack)[0]
def first(self):
self.do_pop()
return list(self._stack)[0]
def do_pop(self):
while self.len() > self._range:
self._stack.popitem(last=False)
self.len()
# end of class
dequeDict = DictionaryDeque(30)
for i in range (0, 40):
now = str(datetime.datetime.now())
dequeDict.add(now, i)
dequeDict.setRange(10)
print(dequeDict.len())
print(dequeDict.last())
print(dequeDict.first())
print(dequeDict.stack())
I have to implement the 'first read and remove' and some more functions, but before I start with that, I would love to know if this the/a way to go, or should there be something better?
Is there a way to avoid the list part in
list(self._stack)[0]
?
BTW, what is a good name for this class? < class name changed
Thank you

Where is the value of objects like lists,ints, and strings stored?

Alright so all lists have __setitem__ and __getitem__ and ints have __add__ __sub__ and such to operate on their value. But where is that value actually stored / how can I reference it? Say I want to make a class imitating an list. It might look something like this
class Memory(object):
def __init__(self):
self.data = []
def __getitem__(self, i):
return self.data[i]
def __setitem__(self, key, item):
self.data[key] = item
This isn't very efficient, and I'd have to most likely write every single method of the class individually, which can span hundreds of lines with multiple classes.
The next best solution to create the class being a child of a list like:
class Memory(list):
...
But you can't edit any of its methods because you can't reference its value. If you changes its __setitem__()
What I was wanting to do with this is to create a list class so I can set the list's and do other operations values all on one lambda. I can't just simply directly call __setitem__(key,item) because you can't input a key outside of the range of the items already present in the list. How would I be able to edit the list's value without calling its __setitem__() method.
I realize this doesn't directly answer your question about "where is the value ... stored", and I'm not sure what you mean by "I can't just simply directly call __setitem__(key,item) because you can't input a key outside of the range of the items already present in the list," but if I understand you correctly, you just have to "fill in" values in between the value you want to set and the current length.
For example, I have a utility class that I sometimes use to do this. (See the test cases at the bottom for an idea of how I use it.)
class DynamicArray(list):
''' Just like a normal list except you can skip indices when you fill it. '''
def __init__(self, defaultVal = None, startingCapacity = 0):
super(DynamicArray, self).__init__()
self.defaultVal = defaultVal
if (startingCapacity > 0):
self += [ defaultVal ] * startingCapacity
def insert(self, ind, val):
if (ind > len(self)):
for i in xrange(len(self), ind):
self.append(self.defaultVal)
super(DynamicArray, self).insert(ind, val)
def set(self, ind, val):
self[ind] = val
def __setitem__(self, ind, val):
if (ind >= len(self)):
for i in xrange(len(self), ind + 1):
self.append(self.defaultVal)
super(DynamicArray, self).__setitem__(ind, val)
if __name__ == "__main__":
a = DynamicArray()
assert(len(a) == 0)
a[3] = 2
assert(a[3] == 2)
assert(a[0] is None and a[1] is None and a[2] is None)
assert(len(a) == 4)
a[1] = 1
assert(a[1] == 1)
assert(a[3] == 2)
assert(a[0] is None and a[2] is None)
assert(len(a) == 4)
a[5] = 7
assert(a[5] == 7)
assert(a[3] == 2)
assert(a[1] == 1)
assert(a[0] is None and a[2] is None)
assert(len(a) == 6)

OO Black Jack Game - computing hand values

Running the program will cause an error message:
TypeError: unsupported operand type(s) for +=: 'int' and 'NoneType'
The problem is with the line value += values.get(card.get_rank)
I think there may be a problem with the get_rank method? Does it not return an integer?
ranks = ('A','2','3','4','5','6','7','8','9','10','J','Q','K')
values = {'A':1,'2':2,'3':3,'4':4,'5':5,'6':6,'7':7,'8':8,'9':9,'10':10,'J':10,'Q':10,'K':10}
suits = ('Diamonds','Hearts','Clubs','Diamonds')
class Card:
def __init__(self, suit, rank):
self.suit = suit
self.rank = rank
def __str__(self, suit, rank):
print (self.rank + 'of' + self.rank)
def get_rank(self):
return self.rank
class Hand:
def __init__(self):
self.hand = []
def __str__(self):
hand = ''
for card in self.hand:
hand = hand + str(card)
return hand
def get_value(self):
value = 0
aces = 0
for card in self.hand:
if card.get_rank == 'A':
aces += 1
value += values.get(card.get_rank)
if (aces>0) and (value + 10 <= 21):
value += 10
return value
values.get(card.get_rank) tries to use the instance method as the key for the dictionary. This is not a valid key in the dictionary, so dict.get() returns the default None.
Instead you want to call the method, and use the return value as the key:
value += values.get(card.get_rank())
or, as trivial getters and setters are unpythonic, just access the attribute directly:
value += values.get(card.rank)
Note that you can also pass a default to dict.get() to ensure you always get a sensible return value:
value += values.get(card.rank, 0)
Now if there is no value for that card rank in values, its value is assumed to be zero.
Also, it's not clear where values is coming from. I would suggest you make it a class attribute:
class Hand:
VALUES = {...}
...
def get_value(self):
...
value += self.VALUES.get(card.rank, 0)
...
Or an explicit argument to get_value:
class Hand:
...
def get_value(self, values):
...
value += self.values.get(card.rank, 0)
...

Python greedy thief

Why is it giving me an error " 'int' object is not subscriptable " when i run the program? I looked if i was doing anything wrong, i understand it has to be an integer on line 24, but when I'm changing capacity[1] to capacity(int[1]) , it gives me the same error. Any hint would be appreciated.
class Bag():
__slots__=('name', 'weight', 'value')
def mkBag(name, weight, value):
thisBag = Bag()
thisBag.name = name
thisBag.weight = weight
thisBag.value = value
return thisBag
def ratio(treasure):
print(treasure)
print(treasure)
return treasure[2]//treasure[1]
def plunder(treasure, capacity):
treasure = sorted(treasure, key=ratio, reverse=True)
bagLst = []
current = 0
while current < capacity:
if capacity != 0:
if capacity > current[1]:
bagLst.append(mkBag(treasure[0],weight[1],current[2]))
capacity = capacity - current[1]
else:
bagLst.append(mkBag(current[0], capacity, (current[2]/current[1]), capacity))
capacity = 0
return bagLst
def main():
capacity = 10
name = ''
weight = 0
value = 0
treasure = [('silver', 20, 100), ('platinum', 10, 400), ('paladium',10,800), ('diamonds',5,900), ('gold', 10,60)]
bagLst = plunder(treasure, capacity)
for line in bagLst:
print('bagLst')
current is an int:
current = 0
but you are trying to use it as a list:
if capacity > current[1]:
bagLst.append(mkBag(treasure[0],weight[1],current[2]))
capacity = capacity - current[1]
else:
bagLst.append(mkBag(current[0], capacity, (current[2]/current[1]), capacity))
everywhere you use current[index] you are trying to index the integer value.
If you expected current to be a sequence instead, you'd need to set it to one.
I suspect you want to inspect the current treasure to add to the bag; you didn't pick any treasure item however. Something along the lines of:
current = 0
while capacity and current < len(treasure):
item = treasure[current]
current += 1
if capacity > item[1]:
bagLst.append(mkBag(item[0], item[1], item[2]))
capacity = capacity - item[1]
else:
bagLst.append(mkBag(item[0], capacity, (item[2]/item[1]), capacity))
capacity = 0
"int" object not subscriptable means you're trying to do 1234[1]. That doesn't make any sense! You can subscript a string ('abcdefg'[1] == 'b') and a list ([1,2,3,4,5][1] == 2) but you can't get the "nth element" of an integer.
In your line:
# in def plunder(...):
if capacity > current[1]:
You're trying to access the 2nd element of current, which is currently equal to the integer 0. Are you trying to make that a list? What are you expecting to be in current[1]?
Here's a substantially better way to accomplish this
Hey there, so I figured you meant that current[1] was actually item[1], meaning the weight of the item you were looking at. Instead, current was intended to be the running-weight of the bag. Understood! That said, I wrote up a better solution for this: take a look see!
class Treasure(object):
def __init__(self,name,weight=0,value=0,id_=0):
self.name = name
self.weight = weight
self.value = value
self.id = id_ # bootstrap for further development
#property
def ratio(self):
return self.value/self.weight
class BagFullError(ValueError):
pass
class Bag(object):
def __init__(self,owner=None,capacity=10):
self.owner = owner
self.capacity = capacity
self.contents = list()
def __str__(self):
return_value = "CONTENTS:"
for item in self.contents:
return_value += "\n ${0.value:4} {0.name:10}{0.weight} lb".format(item)
return return_value
def add(self,other):
if not isinstance(other,Treasure):
raise TypeError("Must pick up Treasure")
if self.weight + other.weight > self.capacity:
raise BagFullError("Bag cannot fit {}({} lb) ({} lb/{} lb)".format(
other.name,other.weight,self.weight,self.capacity))
self.contents.append(other)
def remove(self,other):
self.contents.remove(other)
# may throw ValueError if `other` not in `self.contents`
#property
def weight(self):
return sum(item.weight for item in self.contents)
treasure = [Treasure('silver', 20, 100), Treasure('platinum', 10, 400),
Treasure('paladium',10,800), Treasure('diamonds',5,900),
Treasure('gold', 10,60)]
## map(lambda x: Treasure(*x), [('silver',20,100), ... ])
def plunder(treasure_list,bag=None):
_bag = bag or Bag()
treasures = sorted(treasure_list,
key = lambda x: x.ratio,
reverse = True)
while True:
for treasure in treasures:
try: _bag.add(treasure)
except BagFullError as e:
print(e)
return _bag
bag = Bag("Adam",100)
print(bag)
plunder(treasure,bag)
print(bag)
print("Total Value: {}".format(sum(item.value for item in bag.contents)))

Using a dictionary to control the flow of methods

I have a class Population that contains several method.
According to an input I want the run the method on an instance of the class Population in a given order.
To be a bit more accurate in what I am trying to achieve is quite the same than using is something like that:
stuff = input(" enter stuff ")
dico = {'stuff1':functionA, 'stuff2':functionC, 'stuff3':functionB, 'stuff4':functionD}
dico[stuff]()
Except that the functionA, functionB etc... are methods and not functions:
order_type = 'a'
class Population (object):
def __init__(self,a):
self.a = a
def method1 (self):
self.a = self.a*2
return self
def method2 (self):
self.a += 2
return self
def method3 (self,b):
self.a = self.a + b
return self
if order_type=='a':
order = {1:method1, 2:method2, 3:method3}
elif order_type=='b':
order = {1:method2, 2:method1, 3:method3}
else :
order = {1:method3, 2:method2, 3:method1}
my_pop = Population(3)
while iteration < 100:
iteration +=1
for i in range(len(order)):
method_to_use = order[i]
my_pop.method_to_use() # But obviously it doesn't work!
Hope I've made my question clear enough!
Note that one of my method need two arguments
Pass the instance explicitly as first argument:
method_to_use = order[i]
method_to_use(my_pop)
Full working code:
order_type = 'a'
class Population (object):
def __init__(self,a):
self.a = a
def method1 (self):
self.a = self.a*2
return self
def method2 (self):
self.a += 2
return self
def method3 (self):
self.a = 0
return self
if order_type=='a':
order = [Population.method1, Population.method2, Population.method3]
elif order_type=='b':
order = [Population.method2, Population.method1, Population.method3]
else :
order = [Population.method3, Population.method2, Population.method1]
my_pop = Population(3)
while iteration < 100:
iteration +=1
for method_to_use in order:
method_to_use(my_pop)
If you want to pass more than one argument, simply use the *args syntax:
if order_type=='a':
order = [Population.method1, Population.method2, Population.method3]
arguments = [(), (), (the_argument,)]
elif order_type=='b':
order = [Population.method2, Population.method1, Population.method3]
arguments = [(), (), (the_argument,)]
else :
order = [Population.method3, Population.method2, Population.method1]
arguments = [(the_argument, ), (), ()]
my_pop = Population(3)
while iteration < 100:
iteration +=1
for method_to_use, args in zip(order, arguments):
method_to_use(my_pop, *args)
The () is an empty tuple, hence *args will expand to no additional arguments, while (the_argument,) is a 1-element tuple that will pass the argument to the method.
Use getattr:
order = {1:'method1', 2:'method2', 3:'method3'} #values are strings
...
method_to_use = order[i]
getattr(mypop, method_to_use)()
You can use operator.methodcaller:
from operator import methodcaller
method_to_use = methodcaller('method' + str(i))
method_to_use(my_pop)

Categories

Resources