Operations on Sequences¶
Mutating a list¶
For list (but not tuple), subscription and slicing can also be used as the target of an assignment operation to mutate the list.
%%mytutor -h 300
b = [*range(10)] # aliasing
b[::2] = b[:5]
b[0:1] = b[:5]
b[::2] = b[:5] # fails
Last assignment fails because [::2] with step size not equal to 1 is an extended slice, which can only be assigned to a list of equal size.
What is the difference between mutation and aliasing?
In the previous code:
The first assignment
b = [*range(10)]is aliasing, which gives the list the target name/identifierb.Other assignments such as
b[::2] = b[:5]are mutations that calls__setitem__because the targetb[::2]is not an identifier.
Exercise Explain the outcome of the following checks of equivalence?
%%mytutor -h 400
a = [10, 20, 30, 40]
b = a
print('a is b? {}'.format(a is b))
print('{} == {}? {}'.format(a, b, a == b))
b[1:3] = b[2:0:-1]
print('{} == {}? {}'.format(a, b, a == b))
a is banda == breturnsTruebecause the assignmentb = amakesban alias of the same objectapoints to.In particular, the operation
b[1:3] = b[2:0:-1]affects the same listapoints to.
Why mutate a list?
The following is another implementation of composite_sequence that takes advantage of the mutability of list.
def sieve_composite_sequence(stop):
is_composite = [False] * stop # initialization
for factor in range(2,stop):
if is_composite[factor]: continue
for multiple in range(factor*2,stop,factor):
is_composite[multiple] = True
return (x for x in range(4,stop) if is_composite[x])
for x in sieve_composite_sequence(100): print(x, end=' ')
4 6 8 9 10 12 14 15 16 18 20 21 22 24 25 26 27 28 30 32 33 34 35 36 38 39 40 42 44 45 46 48 49 50 51 52 54 55 56 57 58 60 62 63 64 65 66 68 69 70 72 74 75 76 77 78 80 81 82 84 85 86 87 88 90 91 92 93 94 95 96 98 99
The algorithm
changes
is_composite[x]fromFalsetoTrueifxis a multiple of a smaller numberfactor, andreturns a generator that generates composite numbers according to
is_composite.
Exercise Is sieve_composite_sequence more efficient than your solution composite_sequence? Why?
for x in composite_sequence(10000): pass
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-5-1eba4e6d3969> in <module>
----> 1 for x in composite_sequence(10000): pass
NameError: name 'composite_sequence' is not defined
for x in sieve_composite_sequence(1000000): pass
The line if is_composite[factor]: continue avoids the redundant computations of checking composite factors.
Exercise Note that the multiplication operation * is the most efficient way to initialize a 1D list with a specified size, but we should not use it to initialize a 2D list. Fix the following code so that a becomes [[1, 0], [0, 1]].
%%mytutor -h 250
a = [[0] * 2] * 2
a[0][0] = a[1][1] = 1
print(a)
### BEGIN SOLUTION
a = [[0] * 2 for i in range(2)]
### END SOLUTION
a[0][0] = a[1][1] = 1
print(a)
[[1, 0], [0, 1]]
Different methods to operate on a sequence¶
Recall the quicksort algorithm:
def quicksort(seq):
'''Return a sorted list of items from seq.'''
if len(seq) <= 1:
return list(seq)
i = random.randint(0, len(seq) - 1)
pivot, others = seq[i], [*seq[:i], *seq[i + 1:]]
left = quicksort([x for x in others if x < pivot])
right = quicksort([x for x in others if x >= pivot])
return [*left, pivot, *right]
seq = [random.randint(0, 99) for i in range(10)]
print(seq, quicksort(seq), sep='\n')
[61, 69, 90, 96, 7, 78, 35, 63, 15, 24]
[7, 15, 24, 35, 61, 63, 69, 78, 90, 96]
There is also a built-in function sorted for sorting a sequence:
sorted?
sorted(seq)
[7, 15, 24, 35, 61, 63, 69, 78, 90, 96]
Is quicksort quicker?
%%timeit
quicksort(seq)
12.1 µs ± 45.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
%%timeit
sorted(seq)
209 ns ± 0.414 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Python implements the Timsort algorithm, which is very efficient.
What are other operations on sequences?
The following compares the lists of public attributes for tuple and list.
We determine membership using the operator
inornot in.Different from the keyword
inin a for loop, operatorincalls the method__contains__.
list_attributes = dir(list)
tuple_attributes = dir(tuple)
print(
'Common attributes:', ', '.join([
attr for attr in list_attributes
if attr in tuple_attributes and attr[0] != '_'
]))
print(
'Tuple-specific attributes:', ', '.join([
attr for attr in tuple_attributes
if attr not in list_attributes and attr[0] != '_'
]))
print(
'List-specific attributes:', ', '.join([
attr for attr in list_attributes
if attr not in tuple_attributes and attr[0] != '_'
]))
Common attributes: count, index
Tuple-specific attributes:
List-specific attributes: append, clear, copy, extend, insert, pop, remove, reverse, sort
There are no public tuple-specific attributes, and
all the list-specific attributes are methods that mutate the list, except
copy.
The common attributes
countmethod returns the number of occurrences of a value in a tuple/list, andindexmethod returns the index of the first occurrence of a value in a tuple/list.
%%mytutor -h 300
a = (1,2,2,4,5)
print(a.index(2))
print(a.count(2))
reverse method reverses the list instead of returning a reversed list.
%%mytutor -h 300
a = [*range(10)]
print(reversed(a))
print(*reversed(a))
print(a.reverse())
copymethod returns a copy of a list.tupledoes not have thecopymethod but it is easy to create a copy by slicing.
%%mytutor -h 400
a = [*range(10)]
b = tuple(a)
a_reversed = a.copy()
a_reversed.reverse()
b_reversed = b[::-1]
sort method sorts the list in place instead of returning a sorted list.
%%mytutor -h 300
import random
a = [random.randint(0,10) for i in range(10)]
print(sorted(a))
print(a.sort())
extendmethod that extends a list instead of creating a new concatenated list.appendmethod adds an object to the end of a list.insertmethod insert an object to a specified location.
%%mytutor -h 300
a = b = [*range(5)]
print(a + b)
print(a.extend(b))
print(a.append('stop'))
print(a.insert(0,'start'))
popmethod deletes and return the last item of the list.removemethod removes the first occurrence of a value in the list.clearmethod clears the entire list.
We can also use the function del to delete a selection of a list.
%%mytutor -h 300
a = [*range(10)]
del a[::2]
print(a.pop())
print(a.remove(5))
print(a.clear())