Recursive Class Objects and Member Variables in Python - python

Observe the following code:
class permcom:
def __init__(self, INPUT_SET, IS_PERM, REPETITION):
self.end_set = []
self.input_set = INPUT_SET
self.is_perm = IS_PERM
self.repetition = REPETITION
def helpfunc(self, seen, depth, current):
if depth == 0:
self.end_set.append(seen)
else:
for i in range(0, len(self.input_set)):
if(self.repetition):
seen.append(self.input_set[i])
if(self.is_perm):
self.helpfunc(seen, depth - 1, 0)
else:
self.helpfunc(seen, depth - 1, i)
del seen[-1]
# return all permutations with repetition
def rapwr(INPUT_SET, subset_size):
instance = permcom(INPUT_SET, True, True)
A = []
instance.helpfunc(A, subset_size, 0)
return instance.end_set
A = [1,2,3]
B = rapwr(A, 2)
for i in range(0, len(B)):
print B[i]
The output is the following:
[]
[]
[]
[]
[]
[]
[]
[]
[]
However, the intended output is this:
[1, 1]
[1, 2]
[1, 3]
[2, 1]
[2, 2]
[2, 3]
[3, 1]
[3, 2]
[3, 3]
I've spent way too much time looking at this code and, unfortunately, I still cannot figure out exactly what's wrong. There must be something fundamental that I'm not understanding about how member variables work in Python, but I still don't quite understand what's going on here and why the code isn't working. Can somebody explain this?

Short answer
What you need is list slicing [:]. Changing the statement
if depth == 0:
self.end_set.append(seen)
to
if depth == 0:
self.end_set.append(seen[:])
Gives the expected answer
Long answer
Try this sample code in a python interpreter
a = [1,2]
b = []
b.append(a)
a[0] = 3
print b
# output is [[3, 2]]
Now try this code
a = [1,2]
b = []
b.append(a[:])
a[0] = 3
print b
# output is [[1, 2]]
Why did this happen? In the first case when you appended a to the list b, it was not the value of a that was appended, it was a reference/tag to the [1,2] value. You can verify this by printing id(b[0]) and id(a). Both will be the same value. Hence when you modify any value in the a list, the value in the b list also changes.
Same is the case in your code. Since you are doing del seen[-1], the corresponding value in self.end_set is also removed. You can confirm this by printing the value of self.end_set in the depth == 0 block.
To avoid this you append a clone of one list to the other list. This is done by using the splicing syntax [:]. This creates a copy of the list from the start to the end of the list. You can learn more about slicing here.
PS: Try printing the id() of the two lists when you use slicing, the values will be different
Here is what I got
a = [1,2]
b = []
b.append(a)
print id(b[0])
#output is 43337352L
print id(a)
#output is 43337352L
b = []
b.append(a[:])
print id(b[0])
#output is 43337608L
Take a look at this python memory model diagram for a better understanding of the above
Update: some advice
Since B and self.input_set are both lists, prefer using the idiomatic for i in B and for i in self.input_set.
Make sure your function names are understandable. It might help you out someday. Generally if you are made to write a comment for a variable or function name, it is better to name the function/variable with a shortened version of the comment itself. So rapwr can be renamed to return_all_permutations_with repetition. Though the name is large, its easy to understand what it does without looking at the method body now.

Related

Why python call by reference is so unruly

arr = [1]
def f1(lst):
lst.append(2)
print(lst)
lst = 2
print(lst)
f1(arr)
print(arr) # [1,2]
why python call by reference parameter does not change to value?
what does lst variable indeicating when do "lst = 2"
(not connected to arr?)
Assigning lst = 2 doesn't affect the value of arr. In fact, python doesn't do "call by reference" at all.
Annotating your code with comments that might help clear it up:
arr = [1]
def f1(lst):
# lst and arr both refer to the same [1] list at this point.
# Two different and independent names for the same object.
lst.append(2) # appends 2 to [1], aka lst, aka arr
print(lst) # lst/arr is now [1, 2]
lst = 2 # reassign the name 'lst' to the value 2!
# At this point, lst refers to 2 instead of [1, 2].
# lst and arr are no longer connected.
# arr is still [1, 2] even though lst is 2.
print(lst) # indeed, lst is now 2
# but arr is still [1, 2], as seen below:
f1(arr)
print(arr) # [1,2]
https://nedbatchelder.com/text/names.html is highly recommended reading on this topic! The main thing to understand is that lst and arr are just different names that at different points in the code might refer to the same value or different values.
When you call lst.append, you are modifying the value that lst is a name for, which arr also happens to be a name for. When you say lst = 2, you are rebinding the name lst, but you are not modifying the value that it previously referred to (and to which arr still refers).

Assinging a new value to item in list during for loop doesnt work?

I have a list of items. In a for loop i check if the item is 3. And if it is 3 then it should change the 3 to a 2. These are the two ways that came to my mind using a for loop. But only the last one does work.
Is there a way I can make the first for loop work without losing its "pythonic" style?
a = [1, 2, 3]
for num in a:
if num == 3:
num = 2
# -> [1, 2, 3]
for i in range(len(a)):
if a[i] == 3:
a[i] = 2
# -> [1, 2, 2]
There's no way to assign to a bare name (num) and have it affect a container (list a). You need to use an index. Although, it is more Pythonic to use enumerate:
for i, num in enumerate(a):
if num == 3:
a[i] = 2
Another option is to use a full-slice assignment to totally overwrite the contents of the list from a generator expression:
a[:] = (2 if num==3 else num for num in a)
Let's take a look at this code:
for i in [1, 2, 3]:
...
In the for loop, the [1, 2, 3] is a list object. The i is just a variable that holds a pointer (basically a reference to the data). When you do an operation like i = 3 in the loop, the variable i is set to hold the number 3, but the actual list is not changed. List comprehension can be used for what you're trying to accomplish:
a = [1, 2, 3]
l = [2 if num == 3 else num for num in a]
# note that this is a ternary operator in a list comprehension
If you wish to use a for loop, then then enumerate method with index assignment will do the trick:
a = [1, 2, 3]
for i, num in enumerate(a):
if num == 3:
a[i] = 2
You can also do it manually like so:
i = 0
for num in a:
if num == 3:
a[i] = 2
i += 1
Note that:
list comprehension creates a new list (and doesn't edit the old one)
the enumeration method I showed above does edit the original list (but may be slower, this is based on your machine though)
the final option I put just to illustrate what the enumerate method does and to show that it is an option
To modify in-place, this is perhaps more Pythonic:
for i, n in enumerate(a):
if n == 3:
a[i] = 2
This may also be preferable if you have a lengthy test for how an item is replaced, such that a list comprehension may be unwieldy.
Try a list comprehension:
>>> a = [1, 2, 3]
>>> a = [2 if num == 3 else num for num in a]
>>> a
[1, 2, 2]
If not a list comprehension, you could do use an enumeration:
a = [1,2,3]
for count, num in enumerate(a):
if num == 3:
a[count] = 2
A map also works (seriously just use a list comprehension), but it's a bit less pythonic (lambdas are also confusion to some people):
a = [1,2,3]
a = list(map(lambda num: 2 if num==3 else num, a))
Another thing you can do is, if the variable you're iterating over is some object with a method containing side effects (like a setter), you could use that in-place. For example, imagine I have some myInteger class that inherits from int with a method that lets me set some value. Let's say it's something like:
myInt = myInteger(5)
print(myInt.value)
>> 5
myInt.set_value(6)
print(myInt.value)
>>6
This would give you the interface you're looking for:
for num in a:
if num == 3:
a.set_value(2)
The trade-off being that you're writing a weird class to do this, which will lead to confusion in the future.

Get complement (opposite) of list slice

Is there syntax to get the elements of a list not within a given slice?
Given the slice [1:4] it's easy to get those elements:
>>> l = [1,2,3,4,5]
>>> l[1:4]
[2, 3, 4]
If I want the rest of the list I can do:
>>> l[:1] + l[4:]
[1, 5]
Is there an even more succinct way to do this? I realize that I may be being too needy because this is already very concise.
EDIT: I do not think that this is a duplicate of Invert slice in python because I do not wish to modify my original list.
If you want to modify the list in-place, you can delete the slice:
>>> l = [1, 2, 3, 4, 5]
>>> del l[1:4]
>>> l
[1, 5]
Otherwise your originally suggestion would be the most succinct way. There isn't a way to get the opposite of a list slice using a single slice statement.
Clearly the best solution to create a class to encapsulate some magical behavior that occurs when you use 'c' as the step value. Clearly.
class SuperList(list):
def __getitem__(self, val):
if type(val) is slice and val.step == 'c':
copy = self[:]
copy[val.start:val.stop] = []
return copy
return super(SuperList, self).__getitem__(val)
l = SuperList([1,2,3,4,5])
print l[1:4:'c'] # [1, 5]
[x for i, x in enumerate(l) if i not in range(1, 4)]
Which is less concise. So the answer to your question is no, you can't do it more concisely.
I was looking for some solution for this problem that would allow for proper handling of the step parameter as well.
None of the proposed solution was really viable, so I ended up writing my own:
def complement_slice(items, slice_):
to_exclude = set(range(len(items))[slice_])
step = slice_.step if slice_.step else 1
result = [
item for i, item in enumerate(items) if i not in to_exclude]
if step > 0:
return result
else:
return result[::-1]
ll = [x + 1 for x in range(5)]
# [1, 2, 3, 4, 5]
sl = slice(1, 4)
ll[sl]
# [2, 3, 4]
complement_slice(ll, sl)
# [1, 5]
To the best of my knowledge, it does handle all the corner cases as well, including steps, both positive and negative, as well as repeating values.
I wanted to write it as a generator, but I got annoyed by checking all corner cases for positive/negative/None values for all parameters.
In principle, that is possible, of course.
You can use list comprehension with loop
l = [i for i in l if i not in l[1:4]]

Remove matching items in a list

Sorry if this is a duplicate question, I searched and couldn't find anything to help.
I'm currently trying to compare two lists. If there are any matching items I will remove them all from one of the lists.
However the results I have are buggy. Here is a rough but accurate representation of the method I'm using:
>>> i = [1,2,3,4,5,6,7,8,9]
>>> a = i
>>> c = a
>>> for b in c:
if b in i:
a.remove(b)
>>> a
[2, 4, 6, 8]
>>> c
[2, 4, 6, 8]
So I realised that the main issue is that as I remove items it shortens the list, so Python then skips over the intermediate item (seriously annoying). As a result I made a third list to act as an intermediate that can be looped over.
What really baffles me is that this list seems to change also even when I haven't directly asked it to!
In python, when you write this:
i = [1,2,3,4,5,6,7,8,9]
You create an Object (in this case, a list) and you assign it to the name i. Your next line, a = i, tells the interpreter that the name a refers to the same Object. If you want them to be separate Object you need to copy the original list. You can do that via the slicing shorthand, i[:], or you can use a = list(i) to be more explicit.
The easiest way to do this is use a set to determine shared items in a and b:
for x in set(a).intersection(b):
a.remove(x)
Your statements a = i and c = a merely make new names that reference the same object. Then as you removed things from a, it's removed from b and i, since they are the same object. You'll want to make copies of the lists instead, like so
a = i[:]
c = a[:]
a = i Doesn't make a copy of a list, it just sets another variable, i to point at your list a. Try something like this:
>>> i = [1, 2, 3, 2, 5, 6]
>>> s = []
>>> for i in t:
if i not in s:
s.append(i)
>>> s
[1, 2, 3, 5, 6]
You can also use set which guarantees no duplicates, but doesn't preserve the order:
list(set(i))

Popping all items from Python list

I'm wondering if there is a way to "pop all" items from a list in Python?
It can be done in a few lines of code, but the operation seems so simple I just assume there has to be a better way than making a copy and emptying the original. I've googled quite a bit and searched here, but to no avail.
I realize that popping all items will just return a copy of the original list, but that is exactly why I want to do just that. I don't want to return the list, but rather all items contained therein, while at the same time clearing it.
class ListTest():
def __init__(self):
self._internal_list = range(0, 10)
def pop_all(self):
result, self._internal_list = self._internal_list[:], []
return result
# ... instead of:
# return self._internal_list.pop_all()
t = ListTest()
print "popped: ", t.pop_all()
print "popped: ", t.pop_all()
... which of course returns the expected:
popped: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
popped: []
This is exactly how it's done. The only substantial improvement that I can think of is to empty the list in-place:
def pop_all(l):
r, l[:] = l[:], []
return r
The difference between this and your version is the behavior on a list that is referenced from various places:
>>> a = [1, 2, 3]
>>> b = a
>>> pop_all(a)
[1, 2, 3]
>>> b
[]
In fact, why not just
def pop_all(self):
result, self._internal_list = self._internal_list, []
return result
... if you are reassigning self._internal_list anyway, why not just return the old one instead of copying it?
I had a need for this just now, and decided on using a list comprehension rather than have a subclass or subroutine like this:
popped = [l.pop(0) for item in list(l)]

Categories

Resources