Python decorating property setter with list [duplicate] - python

This question already has answers here:
python: how to have a property and with a setter function that detects all changes that happen to the value
(3 answers)
Closed 6 years ago.
globalList = []
class MyList:
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.myList.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
Result:
myList: [1, 2, 3]
after appending a 4, myList: [1, 2, 3]
after extend, myList: [1, 2, 3]
The problem I am facing is that mL1.myList.append(4) and mL1.myList.extend([5,6,"eight","IX"]) do not modify the _myList attribute in the mL1 object. How could I do to resolve the problem?

I define a method append() and a method extend() for the class object. It respectively appends to member myList and extends member myList.
global globalList
globalList = []
class MyList():
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
def append(self, val):
self.myList = self.myList + [val]
return self.myList
def extend(self, val):
return self.myList.extend(val)
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
result is
>>>
('myList: ', [1, 2, 3])
('after appending a 4, myList: ', [1, 2, 3, 4])
('after extend, myList: ', [1, 2, 3, 4, 5, 6, 'eight', 'IX'])

I would subclass list and override a few methods:
import itertools
class ExtendedList(list):
def __init__(self, other=None):
self.other = other or []
def __len__(self):
return list.__len__(self) + len(self.other)
def __iter__(self):
return itertools.chain(list.__iter__(self), iter(self.other))
def __getitem__(self, index):
l = list.__len__(self)
if index > l:
return self.other[index - l]
else:
return list.__getitem__(self, index)
It should work with just about everything:
In [9]: x = ExtendedList([1, 2, 3])
In [10]: x
Out[10]: [1, 2, 3]
In [11]: x.append(9)
In [12]: x
Out[12]: [9, 1, 2, 3]
In [13]: x.extend([19, 20])
In [14]: x
Out[14]: [9, 19, 20, 1, 2, 3]
In [15]: sum(x)
Out[15]: 54

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)

Optimization 8-puzzle

I'm junior programmer, I am trying to solve 8-puzzle problem with breadth first search, but it took too long time to solve it, i want to optimize my code.
Configuration: [[5, 4, 3], [0, 7, 2], [6, 1, 8]] is going to solve in 22.623718615 seconds,
configuration [[8, 0, 6], [5, 4, 7], [2, 3, 1]] took among 235.721346421 seconds.
I want to decrease solve time.
There is my code:
from copy import deepcopy
from collections import deque
from time import perf_counter
most_hard = [[8, 0, 6], [5, 4, 7], [2, 3, 1]] # 30 moves
class CheckPuzzle:
def __init__(self, puzzle: list):
self.puzzle = puzzle
self.len = len(puzzle)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
if not self.is_valid():
raise TypeError("Puzzle is not valid")
elif not self.is_solvable():
raise Exception("Unsolvable puzzle")
# создай ф-ию check
def sum_of_numbers(self) -> int:
return sum(self.convert_to_1d(self.goal))
def sum_of_squares(self) -> int:
return sum([i ** 2 for i in self.convert_to_1d(self.goal)])
def is_valid(self) -> bool:
sum_of_numbers = 0
sum_of_squares = 0
for row in range(self.len):
for column in range(self.len):
sum_of_numbers += self.puzzle[row][column]
sum_of_squares += (self.puzzle[row][column]) ** 2
return sum_of_numbers == self.sum_of_numbers() and sum_of_squares == self.sum_of_squares()
def convert_to_1d(self, board) -> list:
one_dimension_matrix = []
for row in range(self.len):
for column in range(self.len):
one_dimension_matrix.append(board[row][column])
return one_dimension_matrix
def inversion(self, board) -> int:
inversion = 0
one_dimension_matrix = self.convert_to_1d(board)
for index in range(len(one_dimension_matrix)):
temp = one_dimension_matrix[index]
if temp == 0 or temp == 1:
continue
for elem in one_dimension_matrix[index:]:
if elem == 0:
continue
if temp > elem:
inversion += 1
return inversion
def is_solvable(self) -> bool:
inv_of_matrix = self.inversion(self.puzzle)
inv_of_goal_matrix = self.inversion(self.goal)
return (inv_of_matrix % 2 == 0 and inv_of_goal_matrix % 2 == 0) or \
(inv_of_matrix % 2 == 1 and inv_of_goal_matrix % 2 == 1)
class Puzzle:
def __init__(self, board: list):
self.board = board
self.len = len(board)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
def print_matrix(self) -> str:
output = ''
for row in self.board:
for elem in row:
output += str(elem) + " "
output += '\n'
return output
def get_index(self, matrix, value) -> tuple:
for i in range(self.len):
for j in range(self.len):
if matrix[i][j] == value:
return i, j
def manhattan(self):
distance = 0
for i in range(self.len):
for j in range(self.len):
if self.board[i][j] != 0:
x, y = divmod(self.board[i][j] - 1, self.len)
distance += abs(x - i) + abs(y - j)
return distance
def list_of_possible_moves(self) -> list:
x, y = self.get_index(self.board, 0)
possible_moves = []
if x > 0:
possible_moves.append((x - 1, y))
if x < self.len - 1:
possible_moves.append((x + 1, y))
if y > 0:
possible_moves.append((x, y - 1))
if y < self.len - 1:
possible_moves.append((x, y + 1))
return possible_moves
def move(self, to: tuple) -> list:
moving_board = deepcopy(self.board)
x, y = self.get_index(self.board, 0)
i, j = to
moving_board[x][y], moving_board[i][j] = moving_board[i][j], moving_board[x][y]
return moving_board
def solved(self) -> bool:
return self.board == self.goal
def __str__(self) -> str:
return ''.join(map(str, self))
def __iter__(self):
for row in self.board:
yield from row
class Node:
def __init__(self, puzzle, parent=None):
self.puzzle = puzzle
self.parent = parent
if self.parent:
self.g = parent.g + 1
else:
self.g = 0
def state(self) -> str:
return str(self)
def path(self):
node, p = self, []
while node:
p.append(node)
node = node.parent
yield from reversed(p)
def solved(self) -> bool:
return self.puzzle.solved()
def pretty_print(self) -> str:
return self.puzzle.print_matrix()
def h(self) -> int:
return self.puzzle.manhattan()
def f(self) -> int:
return self.h() + self.g
def all_moves(self) -> list:
return self.puzzle.list_of_possible_moves()
def __str__(self) -> str:
return str(self.puzzle)
def make_a_move(self, to: tuple) -> list:
return self.puzzle.move(to)
class GameTree:
def __init__(self, root):
self.root = root
def solve(self):
queue = deque([Node(self.root)])
seen = set()
seen.add(queue[0].state())
while queue:
queue = deque(sorted(list(queue), key=lambda node: node.f()))
node = queue.popleft()
if node.solved():
return node.path()
for move in node.all_moves():
moved = node.make_a_move(move)
child = Node(Puzzle(moved), node)
if child.state() not in seen:
queue.append(child)
seen.add(child.state())
def main():
a = [[5, 4, 3], [0, 7, 2], [6, 1, 8]]
c = Puzzle(a)
d = GameTree(c)
tic = perf_counter()
p = d.solve()
toc = perf_counter()
step = 0
for i in p:
print(i.pretty_print())
step += 1
print(step)
print(toc-tic)
if __name__ == "__main__":
main()
Description:
The 15-puzzle (also called Gem Puzzle, Boss Puzzle, Game of Fifteen,
Mystic Square and many others) is a sliding puzzle that consists of a
frame of numbered square tiles in random order with one tile missing.
The puzzle also exists in other sizes, particularly the smaller
8-puzzle. If the size is 3×3 tiles, the puzzle is called the 8-puzzle
or 9-puzzle, and if 4×4 tiles, the puzzle is called the 15-puzzle or
16-puzzle named, respectively, for the number of tiles and the number
of spaces. The object of the puzzle is to place the tiles in order by
making sliding moves that use the empty space.
https://en.wikipedia.org/wiki/15_puzzle

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]]

Categories