How is sum() used to merge lists - python

I am learning trees in python, which I was showed with these functions to build a tree:
def tree(label, branches=[]):
return [label] + list(branches)
def label(tree):
return tree[0]
def branches(tree):
return tree[1:]
There is a function that can extract all the nodes of a tree into a list:
def all_nodes(tree):
return [label(tree)] + sum([all_nodes(b) for b in branches(tree)], [])
T = tree(1, [tree(2, [tree(4), tree(5)]), tree(3, [tree(6), tree(7)])])
print(all_nodes(T))
# >>> [1, 2, 4, 5, 3, 6, 7]
You can see that this worked very well, but I got confused how sum() is used here.
I know that a list can be added to another list:
print([1] + [2]) # >>> [1, 2]
But I can't make it work by using sum():
a, b = [1], [2]
print(sum(a, b))
# >>> TypeError: can only concatenate list (not "int") to list
print(sum([a, b]))
# >>> TypeError: unsupported operand type(s) for +: 'int' and 'list
In the tree function, how did sum() work to merge all the lists?

The builtin method sum will use the + operation to sum a list of elements. Its second argument is the starting value.
By default the starting value is 0, meaning sum([[1], [2]]) is equivalent to 0 + [1] + [2] which raises a TypeError.
To concatenate lists you want the initial value to be [], an empty list. Then, sum([[1], [2], [3]], []) is equivalent to [] + [1] + [2] + [3] as desired.
Performance
It is not recommended to use sum to concatenate a list of lists. Indeed, on every addition a new list is created. Instead you want to use a solution that traverses all lists and append the items to a new list.
def concat_lists(lists):
new_list = []
for l in lists:
new_list.extend(l)
Or alternatively using itertools.
from itertools import chain
new_list = list(chain(*lists))

sum operates on a sequence of elements, such as sum([1, 2, 3]) (producing 6) or sum([ [1], [2] ], []) (producing [1, 2]). There is an optional second argument, the start value. For instance, sum([1, 2, 3], 10) would start the summation at 10, providing 16. start defaults to 0: if you're summing non-numeric objects, you have to provide a compatible start value.
When you give it sum(a, b), the list a becomes the list of arguments. What sum did was to (correctly) iterate through the items of that list, adding them to the start value you provided. The logic is something like this:
result = b
for element in a:
result = result + element
Thus, the first thing you tried to do was result = [2] + 1. Remember, that first argument is a sequence of the things you want to add up. The most trivial change (although not the most readable) that would let your attempt work is
sum([a], b)
which produces [2, 1], since b is the starting value.
Does that explain what happened?

Related

Why complain that 'tuple' object does not support item assignment when extending a list in a tuple? [duplicate]

This question already has answers here:
a mutable type inside an immutable container
(3 answers)
Closed 6 years ago.
So I have this code:
tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
which generates this error:
TypeError: 'tuple' object does not support item assignment
While this code:
tup = ([1,2,3],[7,8,9])
try:
tup[0] += (4,5,6)
except TypeError:
print tup
prints this:
([1, 2, 3, 4, 5, 6], [7, 8, 9])
Is this behavior expected?
Note
I realize this is not a very common use case. However, while the error is expected, I did not expect the list change.
Yes it's expected.
A tuple cannot be changed. A tuple, like a list, is a structure that points to other objects. It doesn't care about what those objects are. They could be strings, numbers, tuples, lists, or other objects.
So doing anything to one of the objects contained in the tuple, including appending to that object if it's a list, isn't relevant to the semantics of the tuple.
(Imagine if you wrote a class that had methods on it that cause its internal state to change. You wouldn't expect it to be impossible to call those methods on an object based on where it's stored).
Or another example:
>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)
Two mutable lists referenced by a list and by a tuple. Should I be able to do the last line (answer: yes). If you think the answer's no, why not? Should t change the semantics of l3 (answer: no).
If you want an immutable object of sequential structures, it should be tuples all the way down.
Why does it error?
This example uses the infix operator:
Many operations have an “in-place” version. The following functions
provide a more primitive access to in-place operators than the usual
syntax does; for example, the statement x += y is equivalent to x =
operator.iadd(x, y). Another way to put it is to say that z =
operator.iadd(x, y) is equivalent to the compound statement z = x; z
+= y.
https://docs.python.org/2/library/operator.html
So this:
l = [1, 2, 3]
tup = (l,)
tup[0] += (4,5,6)
is equivalent to this:
l = [1, 2, 3]
tup = (l,)
x = tup[0]
x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None
tup[0] = x
The __iadd__ line succeeds, and modifies the first list. So the list has been changed. The __iadd__ call returns the mutated list.
The second line tries to assign the list back to the tuple, and this fails.
So, at the end of the program, the list has been extended but the second part of the += operation failed. For the specifics, see this question.
Well I guess tup[0] += (4, 5, 6) is translated to:
tup[0] = tup[0].__iadd__((4,5,6))
tup[0].__iadd__((4,5,6)) is executed normally changing the list in the first element. But the assignment fails since tuples are immutables.
Tuples cannot be changed directly, correct. Yet, you may change a tuple's element by reference. Like:
>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
The Python developers wrote an official explanation about why it happens here: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
The short version is that += actually does two things, one right after the other:
Run the thing on the right.
assign the result to the variable on the left
In this case, step 1 works because you’re allowed to add stuff to lists (they’re mutable), but step 2 fails because you can’t put stuff into tuples after creating them (tuples are immutable).
In a real program, I would suggest you don't do a try-except clause, because tup[0].extend([4,5,6]) does the exact same thing.

How can I make a recursive function that returns a list of sum of the same index given two lists.?

How can I make a recursive function that returns a list of sum of the same index given two lists.?
I want to make it a recursive function only.
for example:
list1 = [1,2,3]
list2 = [2,4,6]
a recursive function will return a new list [3,6,9]
The language should also be python.
Thank you. I just having a hard time figuring it out.
One approach:
def recursive_sum(a, b):
# if one of the list is empty return the empty list
if not a or not b:
return []
# find the sum of each of the first elements
val = a[0] + b[0]
# return the concatenation of the first sum with the recursive sum for the rest of the lists
return [val, *recursive_sum(a[1:], b[1:])]
Output
[3, 6, 9]
As an alternative suggested by #Stef use:
def recursive_sum(a, b):
if not a or not b:
return []
return [a[0] + b[0]] + recursive_sum(a[1:], b[1:])
The expression:
*recursive_sum(a[1:], b[1:])
is known as unpacking, basically one could said that
[1, 2, 3] is equivalent to [1, *[2, 3]], see this link for more information.

Python: tuple with mutable items [duplicate]

This question already has answers here:
a mutable type inside an immutable container
(3 answers)
Closed 6 years ago.
So I have this code:
tup = ([1,2,3],[7,8,9])
tup[0] += (4,5,6)
which generates this error:
TypeError: 'tuple' object does not support item assignment
While this code:
tup = ([1,2,3],[7,8,9])
try:
tup[0] += (4,5,6)
except TypeError:
print tup
prints this:
([1, 2, 3, 4, 5, 6], [7, 8, 9])
Is this behavior expected?
Note
I realize this is not a very common use case. However, while the error is expected, I did not expect the list change.
Yes it's expected.
A tuple cannot be changed. A tuple, like a list, is a structure that points to other objects. It doesn't care about what those objects are. They could be strings, numbers, tuples, lists, or other objects.
So doing anything to one of the objects contained in the tuple, including appending to that object if it's a list, isn't relevant to the semantics of the tuple.
(Imagine if you wrote a class that had methods on it that cause its internal state to change. You wouldn't expect it to be impossible to call those methods on an object based on where it's stored).
Or another example:
>>> l1 = [1, 2, 3]
>>> l2 = [4, 5, 6]
>>> t = (l1, l2)
>>> l3 = [l1, l2]
>>> l3[1].append(7)
Two mutable lists referenced by a list and by a tuple. Should I be able to do the last line (answer: yes). If you think the answer's no, why not? Should t change the semantics of l3 (answer: no).
If you want an immutable object of sequential structures, it should be tuples all the way down.
Why does it error?
This example uses the infix operator:
Many operations have an “in-place” version. The following functions
provide a more primitive access to in-place operators than the usual
syntax does; for example, the statement x += y is equivalent to x =
operator.iadd(x, y). Another way to put it is to say that z =
operator.iadd(x, y) is equivalent to the compound statement z = x; z
+= y.
https://docs.python.org/2/library/operator.html
So this:
l = [1, 2, 3]
tup = (l,)
tup[0] += (4,5,6)
is equivalent to this:
l = [1, 2, 3]
tup = (l,)
x = tup[0]
x = x.__iadd__([4, 5, 6]) # like extend, but returns x instead of None
tup[0] = x
The __iadd__ line succeeds, and modifies the first list. So the list has been changed. The __iadd__ call returns the mutated list.
The second line tries to assign the list back to the tuple, and this fails.
So, at the end of the program, the list has been extended but the second part of the += operation failed. For the specifics, see this question.
Well I guess tup[0] += (4, 5, 6) is translated to:
tup[0] = tup[0].__iadd__((4,5,6))
tup[0].__iadd__((4,5,6)) is executed normally changing the list in the first element. But the assignment fails since tuples are immutables.
Tuples cannot be changed directly, correct. Yet, you may change a tuple's element by reference. Like:
>>> tup = ([1,2,3],[7,8,9])
>>> l = tup[0]
>>> l += (4,5,6)
>>> tup
([1, 2, 3, 4, 5, 6], [7, 8, 9])
The Python developers wrote an official explanation about why it happens here: https://docs.python.org/2/faq/programming.html#why-does-a-tuple-i-item-raise-an-exception-when-the-addition-works
The short version is that += actually does two things, one right after the other:
Run the thing on the right.
assign the result to the variable on the left
In this case, step 1 works because you’re allowed to add stuff to lists (they’re mutable), but step 2 fails because you can’t put stuff into tuples after creating them (tuples are immutable).
In a real program, I would suggest you don't do a try-except clause, because tup[0].extend([4,5,6]) does the exact same thing.

python 'int' object has no attribute 'sort'

Hey I am new to Python and I have this exercise I want to complete but I get the following error: 'int' object has no attribute 'sort'.
I must use for-loop to take from a list of numbers and make them all square, then print them all out one by one as sorted.
Did I use the sort command incorrectly? Or it doesen't even work with numbers? And do I have to use the .append() command to make them all print out one by one?
So this is my wannabe code so far:
start_list = [5, 3, 1, 2, 4]
square_list = []
for square_list in start_list:
square_list ** 2
print square_list.sort()
There are three issues:
You're assigning from the start_list over your square_list in the for loop.
You're not appending your result into the list (this would fail anyway, because of the above).
The end result is that your square_list is not a list and so cannot be sorted.
Compare the following code:
start_list = [5, 3, 1, 2, 4]
square_list = []
for item in start_list:
square_list.append(item ** 2)
Here we iterate over the list taking an item for each loop. We then square it (using ** 2) and then append it to square_list.
There is also an issue in the print statement: print square_list.sort()
Here you are using .sort() on the list and then trying to print the returned value. But .sort() sorts the list in place and then returns None. So this line will always print None. To print the sorted list you can either use the sorted() function which returns the sorted list, ready to be passed to print:
print sorted(square_list)
Or you can sort the list before printing:
square_list.sort()
print square_list
The most pythonic solution is
start_list = [5, 3, 1, 2, 4]
square_list = [ i ** 2 for i in start_list ]
print(sorted(square_list))
or oneliner:
print(sorted(i ** 2 for i in [5, 3, 1, 2, 4]))
Let's dissect your code:
# here you create an empty list and assign it to
# square list
square_list = []
# yet here you will assign each item of start_list
# to the name square list one by one
for square_list in start_list:
# then you square that number, but it is not stored anywhere
square_list ** 2
# at this point, square_list contains the last element
# of start_list, that is the integer number 4. It does
# not, understandably, have the `.sort` method.
print square_list.sort()
The straightforward fix would be to do:
start_list = [ 5, 3, 1, 2, 4 ]
square_list = []
for element in start_list:
square_list.append(element ** 2)
square_list.sort() # note that printing this would say "None"
print square_list
You must understand for loops.
for square_list in start_list:
square_list ** 2
square_list in this example doesn't refer to the empty list you made. It is used as an ambiguous variable to extract values from a list. As such, this is just the each value in the list during each iteration, and they're all integers because that's what's in your list.
Secondly, you're not actually appending the squares to the list, you're just calculating the squares and doing nothing with them. Lastly, the sort method doesn't return anything, it just alters the list without returning a value. Use the equivalent sorted method which doesn't alter the list but does return a new value. I think you'll best understand with some code.
start_list = [5, 3, 1, 2, 4]
square_list = []
# for loop number is ambiguous variable
for number in start_list:
square = number ** 2 # calculate square to add later
square_list.append(square) # add the calculation
print sorted(square_list) # print sorted version
After loop your square_list is bound to integer [to be precise, it's value is 4].
To create list of squared numbers you may use list comprehensions:
square_list = [i**2 for i in start_list]
And then sort it:
square_list.sort()

Reversing Order of List in Python

I have been given the following piece of code:
def two_pair(ranks):
"""If there are two pair, return the two ranks as a
tuple: (highest, lowest); otherwise return None."""
pair = kind(2,ranks)
lowpair = kind(2, list(reversed(ranks)))
if pair and lowpair != pair:
return (pair,lowpair)
else:
return None
In the lowpair variable, why does list() need to be stated? Why can't you just say reversed(ranks). ranks is a list. Is it not already implied?
reversed returns an iterator, not a list. We need to explicitly convert that to a list, unless we just want to iterate it.
a = [1, 2, 3]
print reversed(a) # <listreverseiterator object at 0x7fc57d746790>
That is why we have to use list to get the actual reversed list, like this
print list(reversed(a)) # [3, 2, 1]
If you want shorter code you could do ranks[::-1] instead of list(reversed(ranks)).
>>> ranks = [1,2,3]
>>> ranks[::-1]
[3, 2, 1]
reversed(ranks) isn't a reversed list. It's an iterator:
>>> reversed([1, 2, 3])
<listreverseiterator object at 0x0000000001DDE9E8>
The list call is necessary to get a reversed list.

Categories