Replace the list element of class - python

This is a homework to select a card from cards. After a card being selected, the card in list should be replaced by None.
class Card():
def __init__(self,cards):
self._cards = cards
def get_cards(self):
return self._cards
def get_card(self,slot):
return self._cards[slot]
It is not allowed to change or add any other code for above class.
And the expected output shows below
card1 = Card([1,2,3,4])
# card1.get_cards()=None (I try this but it does not work because it is not allowed to assign item to method)
print(card1.get_card(0)) # expectation: 1
print(card1.get_cards()) # expectation: [None, 2, 3, 4]
print(card1.get_card(1)) # expectation: 2
print(card1.get_cards()) # expectation: [None, None, 3, 4]

When you pass a list in python, behind the scenes it is just a ref to this list.
So we can use this to our advantage here.
# You almost has done it right
# take the list ref
cards = card1.get_cards()
# and now mutate the list inside
cards[slot] = None
The other solution is to monkey patch the get_card method, so the definition class itself won't be affected in the runtime we will attach new implementation to get_card method. If You need this second solution let me know.

class Card():
def __init__(self,cards):
self._cards = cards
def get_cards(self):
return self._cards
def get_card(self,slot):
return self._cards[slot]
Monkey patching the get_card function
def get_my_card(self, slot):
card, self._cards[slot] = self._cards[slot], None
return card
Time to test the above code
card1 = Card([1,2,3,4])
Card.get_card = get_my_card
print(card1.get_card(0)) # expectation: 1
print(card1.get_cards()) # expectation: [None, 2, 3, 4]
print(card1.get_card(1)) # expectation: 2
print(card1.get_cards()) # expectation: [None, None, 3, 4]
Output
1
[None, 2, 3, 4]
2
[None, None, 3, 4]
Solution 2: Object-Oriented Approach
class Card(): def __init__(self,cards):
self._cards = cards
def get_cards(self):
return self._cards
def get_card(self,slot):
return self._cards[slot]
Create a derived class and overrride get_card method
class MyCard(Card):
def get_card(self, slot):
card, self._cards[slot] = self._cards[slot], None
return card
Test the code
card1 = MyCard([1,2,3,4])
print(card1.get_card(0)) # expectation: 1
print(card1.get_cards()) # expectation: [None, 2, 3, 4]
print(card1.get_card(1)) # expectation: 2
print(card1.get_cards()) # expectation: [None, None, 3, 4]

Related

Why __str__(self) doesn't work when calling the print() function?

I'm diving into OOP and learning magic (or dunder) techniques. Python 3.8.8.
I created class FreqStack() with a pop() method that removes the most frequent elements and returns an updated stack.
class FreqStack():
def __init__(self, lst:list = None):
if lst is None:
self.stack = []
else:
self.stack = lst[::-1]
def push(self, el: int):
self.stack.insert(0, el)
return self.stack
def pop(self):
if len(self.stack) != 0:
hash_map = {}
for el in self.stack:
hash_map[el] = hash_map.get(el, 0) + 1
most_freq_el = max(hash_map, key=hash_map.get)
while most_freq_el in self.stack:
self.stack.remove(most_freq_el)
return self.stack
else:
return 'Stack is empty!'
def __str__(self):
return '\n|\n'.join(str(el) for el in self.stack)
I also added the dunder method str(), which, as far as I understand correctly, must return a custom string when calling the print() function.
However, the print() function in the example below, instead of returning a string, returns a list.
lst = [1, 1, 1, 5, 5, 5, 3, 3, 3, 7, 7, 9]
freq_stack = FreqStack(lst)
for i in range(6):
print(freq_stack.pop())
Output:
[9, 7, 7, 5, 5, 5, 1, 1, 1]
[9, 7, 7, 1, 1, 1]
[9, 7, 7]
[9]
[]
Stack is empty!
I googled everything related to this problem, and couldn't solve it. What am I doing wrong?
You are printing the return value of pop, not the freq_stack itself.
The __str__ method is for the freq_stack object, so you may try something like:
freq_stack.pop()
print(freq_stack)

Problem When Creating Custom Class in Python

With the code below:
class Int_set(list):
def __init__(self):
self.vals=[]
self.index=0
def insert(self, elm): #assume a string of numbers
for x in elm:
if x not in self.vals:
self.vals.append(int(x))
return self.vals
def member(self, elm):
return elm in self.vals
def remove(self, elm):
try:
self.vals.remove(elm)
except:
raise ValueError(str(elm)+' not found')
def get_members(self):
return self.vals[:]
def __str__(self):
if self.vals == []:
return '[]'
self.vals.sort()
result = ''
for e in self.vals:
result = result + str(e) + ','
return f'{{{result[:-1]}}}'
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other))
for e in range(len(other)):
if other[e] not in self.vals:
self.vals.append(other[e])
else:
continue
return self.vals
set1=Int_set()
set1.insert('123456')
print(set1.get_members())
set2=Int_set()
set2.insert('987')
print(set2.get_members())
print(set1.union(set2))
I get output:
[1, 2, 3, 4, 5, 6]
[9, 8, 7]
0 # the print(len(other)) in def union
[1, 2, 3, 4, 5, 6]
Notice that the def union(self, other) did not add in all the unique numbers from set2 to set1. However, if I use:
print(set1.union(set2.vals))
then I get:
[1, 2, 3, 4, 5, 6]
[9, 8, 7]
3 # the print(len(other)) in def union
[1, 2, 3, 4, 5, 6, 9, 8, 7]
What is causing this? I assume set2 inside .union(set2) was in the state when it was initialized(thus it's an empty list). But I inserted some numbers inside and printed it out
You call len on the instance of Int_set. This is derived from list and gives you the length of the instance itself which is 0. You need to overwrite __len__ in your class and give back the value of the instances val.
def __len__(self):
return len(self.vals)
And you have to change your union method too. At the moment you iterate over the members of the instance of Int_set and there are none. You have to iterate over the vals attribute.
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other))
for element in other.vals:
if element not in self.vals:
self.vals.append(element)
else:
continue
return self.vals
Overall it's unclear why you decided to make Int_set a subclass of list since you use no features from the list class.
Your problem is that you are trying to read values from class itself, you are using other instead of other.vals. You have answered your question when you tried with
print(set1.union(set2.vals))
So you can change your function to this:
def union(self, other):
'''add all non-duplicate elements from other set to self set'''
print(len(other.vals))
for e in range(len(other.vals)):
if other.vals[e] not in self.vals:
self.vals.append(other.vals[e])
else:
continue
return self.vals

Why can't my version of a sorted list in Python add a second number?

In the following code, I'm able to add the first number, but I can't add the second number. Inside the class, self correctly updates to [1, 3], but the instance stays at [1]. Why is that and how do I fix it?
from bisect import bisect
class SortedList(list):
def __init__(self):
self = []
def add(self, x):
index = bisect(self, x)
if not self:
self.append(x)
else:
self = self + [x] + self[index:] # after the second call self = [1, 3]
pass
t = 1
for _ in range(t):
n, a, b = 24, 3, 5
if b == 1:
print('yes')
else:
sl = SortedList() # sl = []
st = set()
sl.add(1) # sl = [1]
st.add(1)
i = 0
while sl[i] < n: # when i == 1, there's an error because sl = [1]
num1 = sl[i] * a # num1 = 3
num2 = sl[i] + b
if num1 > sl[i] and num1 not in st:
sl.add(num1) # after this, sl = [1] still
st.add(num1)
if num2 > 1 and num2 not in st:
sl.add(num2)
st.add(num2)
if n in st:
break
i += 1
print('yes' if n in st else 'no')
Don't modify self, when you assign the resulting list to self you change its reference in the local context of the function. But the remote reference keep unchanged.
Better explained in Is it safe to replace a self object by another object of the same type in a method?
By sub-classing list:
import bisect
class SortedList(list):
def append(self, x):
if not self:
super(SortedList, self).append(x)
else:
idx = bisect.bisect(self, x)
self.insert(idx, x)
And then:
>>> import random
>>> l = SortedList()
>>> for i in range(10):
... l.append(random.randint(0, 100))
>>> print(l)
[5, 31, 50, 58, 69, 69, 70, 78, 85, 99]
In order to keep the list sorted you should also override some magic methods such __add__, __iadd__ ...
By wrapping list:
class SortedList(object):
def __init__(self):
self._data = []
def append(self, x):
if not self:
self._data = [x]
else:
idx = bisect.bisect(self, x)
# Here you can change self._data
# Even `insert` is better, because it don't need to copy the list
# you can do
self._data = self._data[:idx] + [x] + self._data[idx:]
But it's very partial, in order to have SortedList look like a list you have to implement the sequence protocol.
self = []
...
self = self + [x] + self[index:]
This doesn't do what you think it does. Whenever you write a = ... (without indexing on the left hand side, which is special) you don't actually change what a is. Instead, you just take whatever is on the right hand side and give it the name a, ignoring whatever a was before.
Example:
>>> a = [1, 2, 3]
>>> b = a
>>> a is b
True
>>> id(a), id(b)
(140484089176136, 140484089176136)
>>> a.append(4) # Modification.
>>> b
[1, 2, 3, 4]
>>> a = a + [5] # Modification? No! We create a new object and label it 'a'.
>>> a
[1, 2, 3, 4, 5]
>>> b
[1, 2, 3, 4]
>>> a is b
False
>>> id(a), id(b)
(140484995173192, 140484089176136)
What you want is bisect_left and to use list.insert:
index = bisect_left(li, x)
li.insert(index, x)
Thanks, everyone. I was able to get the code to work as expected by changing the class as follows:
class SortedList(list):
def add(self, x):
index = bisect(self, x)
self.insert(index, x)

How can we unpack an arbitrarily nested iterator, of iterators, of [...], of iterators?

If we have an iterator of non-iterators, then we can unroll (unpack) it as follows:
unroll = lambda callable, it: callable(it)
inputs = range(0, 10)
print(unroll(list, inputs))
# prints "[1, 2, 3, 4, 5, 6, 7, 8, 9]"
If we have an iterator of iterators or non-iterators, then we can unroll it as follows:
unroll = lambda callable, it: callable(map(callable, it))
inputs = [range(0, 2), range(2, 4), range(4, 6)]
print(unroll(list, inputs))
# prints "[[0, 1], [2, 3], [4, 5]]"
I don't want to flatten the iterator. A flattening of [[0, 1], [2, 3], [4, 5]] is [0, 1, 2, 3, 4, 5] I want to preserve nesting, but have fully-populated containers (lists, tuples, arrays, etc...) instead of iterators.
The question is, how can we unroll an iterator of arbitrarily nested iterators? My attempt is shown below, but it doesn't work.
import abc
class StringPreservationistBase(abc.ABC):
#abc.abstractmethod
def __str__(i):
raise NotImplementedError()
class StringPreservationist(StringPreservationistBase):
"""
The idea behind this class if you get
something which requires calculation, then
the result is stored for future read-like
operations until such a time that the value
becomes stale.
For example, if this was a `Square` class
`Square.get_area()` would only compute `length*width`
the first time.
After that, `Square.get_area()` would simply returned
the pre-calculated value stored in `area`.
If any member variable which `Square.getarea()`
reads from are written to, then the process resets.
That is, if `length` or `width` were written to,
then we go back to the implementation of
`Square.getarea()` which calculates `length*width`
For this particular class the result of
`__str__` is stored.
"""
# Any method with write permission
# is supposed to set state back to StringPreservationistState0
#
# That is, if string become stale, we
# delete the string
#
def __init__(i, elem, count: int):
i._count = count
i._elem = elem
i._state = i._StringPreservationistState0(i)
def __len__(i):
return i._count
def __iter__(i):
return itts.repeat(i._elem, i._count)
def __str__(i):
stryng = str(i._state)
i._state = i._StringPreservationistState1(i, stryng)
return stryng
class _StringPreservationistState1(StringPreservationistBase):
def __init__(i, x, stryng: str):
i._x = x
i._stryng = stryng
def __str__(i):
return i._stryng
class _StringPreservationistState0(StringPreservationistBase):
def __init__(i, x):
i._x = x
def __str__(i):
# s = '',join(itts.repeat(i._x._elem, i._x._count))
s = ''.join(str(x) for x in i._x)
return s
class Spacer(StringPreservationistBase):
def __init__(i, count: int):
i._innerself = StringPreservationist(" ", count)
def __len__(i):
return len(i._innerself)
def __iter__(i):
return iter(i._innerself)
def __str__(i):
return str(i._innerself)
# end class
def indent_print(parent, indent=Spacer(0)):
assert(not isinstance(parent, type("")))
# "a"[0][0][0][0][0][0] == "a"[0]
try:
for child in parent:
nxt_indent = type(indent)(4 + len(indent))
indent_print(child, nxt_indent)
except: # container not iterable
print(indent, parent)
# def get_indent_iter(parent, indent=Spacer(0)):
# try:
# for child in parent:
# it = indent_print(child, type(indent)(4 + len(indent)))
# yield something
# except: # container not iterable
# yield indent
# yield parent
def rasterize_dot_verify_args(callable, parent):
if not hasattr(callable, "__call__"):
raise ValueError()
import inspect
siggy = inspect.signature(callable)
if (len(siggy.parameters) > 1):
raise ValueError()
def rasterize(callable, xparent, make_copy:bool = False):
rasterize_dot_verify_args(callable, xparent)
iparent = xparent
if make_copy:
import copy
iparent = copy.deepcopy(xparent)
if hasattr(iparent, "__iter__"):
iter_kids = iter(iparent)
if iter_kids != iparent:
# ----------------------------------
# why
# iter_kids != parent
# ?!???
# ----------------------------------
# because a single character string
# returns an iterator to iti.
#
# "a"[0][0][0][0][0][0][0][0] = a[0]
# iter(iter(iter(iter("a")))) == iter("a")
#
lamby = lambda p, *, c=callable: rasterize(c, p)
out_kids = map(lamby, iter_kids)
r = callable(out_kids)
else: # iter_kids == iparent
r = callable(iter_kids)
else: # `parent` is not iterable
r = iparent
return r
# iterator to non-iterables
# [1, 2, 3, 4]
input0 = "iter([1, 2, 3, 4])"
# iterator to iterators of non-iterables
import itertools as itts
input1A = "map(lambda x: itts.repeat(x, 6), range(1, 5))"
input1B = "iter([range(0, 2), range(1, 3), range(2, 4)])"
# input1A = [
# [1, 1, 1, 1, 1, 1]
# [2, 2, 2, 2, 2, 2]
# ...
# [2, 2, 2, 2, 2, 2]
# ]
# input1B = [
# [0, 1]
# [1, 2]
# [2, 3]
# ]
inputs = [input0, input1A, input1B]
import copy
for input in inputs:
print(256 * "#")
print(input)
print(list)
iterator = eval(input)
raster = rasterize(list, input)
indent_print(raster)
print(256*"#")
You can try the following function:
def is_iter(i):
if isinstance(i, str):
return len(i) != 1
return hasattr(i, '__iter__')
def unroll(func, iterator):
def _unroll(it): # recursive helper method
if not is_iter(it):
yield it
for i in it:
if not is_iter(i):
yield i
else:
yield func(_unroll(i)) # apply function to iterator and recurse
return func(_unroll(iterator)) # apply function to end result
>>> inputs = [(0,3), '345']
>>> print(unroll(list, inputs))
[[0, 3], ['3', '4', '5']]
>>> inputs = [range(0, 2), range(2, 4), range(4, 6)]
>>> print(unroll(tuple, inputs))
((0, 1), (2, 3), (4, 5))
>>> print(unroll(list, inputs))
[[0, 1], [2, 3], [4, 5]]
>>> inputs = [[range(0, 2), range(2, 4)], range(4, 6)]
>>> print(unroll(tuple, inputs))
(((0, 1), (2, 3)), (4, 5))
>>> print(unroll(list, inputs))
[[[0, 1], [2, 3]], [4, 5]]

Called methods (with the same name) from different parent class, but results appear code only runs one of them

I built three kinds of dices, then use a cup to wrap it. Cup inherits two classes (which inherit the same base class). In cup, I called three roll methods from the two parent classes and the base class.
When I run cup.roll, I should get three numbers, the first one should range from 1-6, second 1-10, third 1-20. But I ran many times, and the results show all the three numbers range from 1-10, which means only the roll method only derives from the one of the parent class.
Why did this happen? And how to solve this issue? I'm just starting with Python.
Update
The problem above is solved by change the structure of Cup, but I have got a new issue.
Now, the code works fine until the last part when I output the result, there should be three list returned from three dices, but the last two are empty, where, when roll and sum, they aren't empty. Very weird.
Output:
# >>> c = cup(2,3,4)
# 2 3 4
# >>> c.roll()
# (when print out roll in dices)
# [5, 2]
# [2, 6, 2]
# [6, 6, 2, 5]
# (but, when print out roll in cup, two become empty)
# [5, 2] [] []
# 36
Code:
class SixSidedDie:
sides = 6
valueLst = []
sumVal = int()
def __init__(self, nOfDie):
self.nOfDie = nOfDie
def roll(self):
import random
self.valueLst = []
for i in range(1, self.nOfDie+1):
self.valueLst.append(random.randint(1,self.sides))
print(self.valueLst)
return self.valueLst
def sum(self):
self.sumVal = 0
for i in self.valueLst:
self.sumVal += i
return self.sumVal
def getFaceValue(self):
return self.valueLst
def __repr__(self):
return 'SixSidedDie({})'.format(self.faceValue)
class TenSidedDie(SixSidedDie):
sides = 10
def __repr__(self):
return 'TenSidedDie({})'.format(self.faceValue)
class TwentySidedDie(SixSidedDie):
sides = 20
def __repr__(self):
return 'TwentySidedDie({})'.format(self.faceValue)
class cup():
sixSDLst = []
tenSDLst = []
twentySDLst = []
def __init__(self, nOfSixSdDie = 1, nOfTenSdDie = 1, nOfTwentySdDie = 1):
self.sixSdDie = SixSidedDie(nOfSixSdDie)
self.tenSdDie = TenSidedDie(nOfTenSdDie)
self.twentySdDie = TwentySidedDie(nOfTwentySdDie)
print(nOfSixSdDie, nOfTenSdDie, nOfTwentySdDie)
def roll(self):
self.sixSDLst = self.sixSdDie.roll()
self.tenSDlst = self.tenSdDie.roll()
self.twentySDlst= self.twentySdDie.roll()
print(self.sixSDLst,self.tenSDLst,self.twentySDLst)
return self.sixSdDie.sum() + self.tenSdDie.sum() + self.twentySdDie.sum()
def getSum(self):
return self.sixSdDie.sum()+ self.tenSdDie.sum() + self.twentySdDie.sum()
def __repr__(self):
output = ''
for i in self.sixSDLst:
output = output + 'SixSidedDie(' + str(i) +'),'
for j in self.tenSDLst:
output = output + 'TenSidedDie(' + str(i) +'),'
for k in self.twentySDLst[:-1]:
output = output + 'TwentySidedDie(' + str(i) +'),'
output = 'Cup(' + output + 'TwentySidedDie(' + str(self.twentySDLst[-1]) +')' + ')'
return output
First, as others have said, you're using inheritance wrong, but I'm chalk that up to the homework requirements.
in your Dice subclasses try updating the number of sides in the __init__ method:
class TenSidedDie(SixSidedDie):
def __init__(self):
self.sides = 10
def __repr__(self):
return 'TenSidedDie({})'.format(self.faceValue)
Also, when calling roll you don't need to include self.
for k in range(1, self.nOfTwentySdDie+1):
self.fVOfTwentySdDie.append(TwentySidedDie.roll())
With your updated code:
Commenting out the global initialized lists worked for me.
As a side note, when printing information you want to be able to look at the logs and immediately understand it. I suggest adding the following line to roll roll method: print('Rolling [{}], count: [{}] values: {}'.format(self.sides, self.nOfDie, self.valueLst)) This will tell you what size of dice you are rolling, how many, and the results of each. No more parsing logs.
Since you're only having technical issues and not asking for your homework to be done for you, this is the code that I have that works for me. I did some minor rewrites to make it more manageable for me and added logging:
import random
class SixSidedDie:
sides = 6
valueLst = []
sumVal = int()
def __init__(self, nOfDie):
print('intializing d6 with [{}] dice'.format(nOfDie))
self.nOfDie = nOfDie
def roll(self):
self.valueLst = []
for i in range(1, self.nOfDie+1):
self.valueLst.append(random.randint(1,self.sides))
print('Rolling {} d{} values: {}'.format(self.nOfDie, self.sides, self.valueLst))
return self.valueLst
def sum(self):
self.sumVal = 0
for i in self.valueLst:
self.sumVal += i
return self.sumVal
def getFaceValue(self):
return self.valueLst
def __repr__(self):
return 'SixSidedDie({})'.format(self.faceValue)
class TenSidedDie(SixSidedDie):
def __init__(self, nOfDie):
print('intializing d10 with [{}] dice'.format(nOfDie))
self.nOfDie = nOfDie
self.sides = 10
def __repr__(self):
return 'TenSidedDie({})'.format(self.faceValue)
class TwentySidedDie(SixSidedDie):
def __init__(self, nOfDie):
print('intializing d20 with [{}] dice'.format(nOfDie))
self.nOfDie = nOfDie
self.sides = 20
def __repr__(self):
return 'TwentySidedDie({})'.format(self.faceValue)
class cup():
def __init__(self, nOfSixSdDie = 1, nOfTenSdDie = 1, nOfTwentySdDie = 1):
self.sixSdDie = SixSidedDie(nOfSixSdDie)
self.tenSdDie = TenSidedDie(nOfTenSdDie)
self.twentySdDie = TwentySidedDie(nOfTwentySdDie)
def getSum(self):
return self.sixSdDie.sum()+ self.tenSdDie.sum() + self.twentySdDie.sum()
def roll(self):
self.sixSdLst = self.sixSdDie.roll()
self.tenSdLst = self.tenSdDie.roll()
self.twentySdLst = self.twentySdDie.roll()
return self.getSum()
def __repr__(self):
output = ''
print('6 sided : [{}]\n10 sided: [{}]\n20 sided: [{}]'.format(len(self.sixSdLst), len(self.tenSdLst), len(self.twentySdLst)))
for i in self.sixSdLst:
output += 'SixSidedDie(' + str(i) +'),'
output += '\n'
for j in self.tenSdLst:
output += 'TenSidedDie(' + str(j) +'),'
output += '\n'
for k in self.twentySdLst:
output += 'TwentySidedDie(' + str(k) +'),'
result = 'Cup(' + output + ')'
return result
play_cup = cup(20, 20, 20)
print('ROLL:', play_cup.roll())
print('SUM:', play_cup.getSum())
print(play_cup)
Results:
intializing d6 with [5] dice
intializing d10 with [5] dice
intializing d20 with [5] dice
Rolling 5 d6 values: [1, 2, 2, 2, 3]
Rolling 5 d10 values: [6, 4, 5, 7, 3]
Rolling 5 d20 values: [9, 8, 2, 5, 10]
ROLL: 69
6 sided : [5]
10 sided: [5]
20 sided: [5]
[1, 2, 2, 2, 3]
[6, 4, 5, 7, 3]
[9, 8, 2, 5, 10]
Cup(SixSidedDie(1),SixSidedDie(2),SixSidedDie(2),SixSidedDie(2),SixSidedDie(3),
TenSidedDie(6),TenSidedDie(4),TenSidedDie(5),TenSidedDie(7),TenSidedDie(3),
TwentySidedDie(9),TwentySidedDie(8),TwentySidedDie(2),TwentySidedDie(5),TwentySidedDie(10),)
It is happening because sides are inherited form TenSidedDie which you then pass to each roll as self. Add print(self.sides) at the start of your cup definition to see it in action.
From python class docs:
class DerivedClassName(Base1, Base2, Base3):
<statement-1>
.
.
.
<statement-N>
For most purposes, in the simplest cases, you can think of the search
for attributes inherited from a parent class as depth-first,
left-to-right, not searching twice in the same class where there is an
overlap in the hierarchy. Thus, if an attribute is not found in
DerivedClassName, it is searched for in Base1, then (recursively) in
the base classes of Base1, and if it was not found there, it was
searched for in Base2, and so on.
you can do something like this to solve after removing the inheritance:
six_die = SixSidedDie()
for i in range(1, self.nOfSixSdDie+1):
self.fVOfSixSdDie.append(six_die.roll())

Categories