Sequence Types - Operations¶
Indices and tables¶
Sequence Operations¶
All of the sequence types support a common set of operations which makes working back and forth between them very fluid and easy.
The mutable types support an additional set that take advantage of the fact that you can modify their contents without creating a new object. These additional methods can be used to mimic other types such as FIFOs and queues.
Common Sequence Operations¶
Individual items in a sequence can be indexed using the item access operator []
, which takes an item index, as in, [idx]
.
Indexes start counting on the left from 0 and increase moving to the right.
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[0]
'a'
>>> foo[4]
'e'
Indices can also be negative, in which case, counting starts from the right and moves left.
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[-1]
'e'
>>> foo[-5]
'a'
Tip
The value, -1, is commonly used as a marker to say, “last item”.
Positive and negative indexing can be described graphically as in Fig. 30:
If you specify an index value that is out of bounds, you will get a IndexError
.
Sequences also support slicing and striding, which allows for obtaining a sub-sequence. Slices use the same item access operator as indexing, [idx]
, but have a more advanced syntax involving the use of a :
which allows specifying a stop and step (i.e. stride) value, as in, [start:stop:step]
. Additionally, all of start
, stop
, and step
are optional and sensible default options are provided:
start
defaults to the 0 for positivestep
, otherwise -1end
defaults to the sequence length (i.e. end of sequence)step
defaults to 1
For example, extracting a linear sub-sequence:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[1:4]
['b', 'c', 'd']
The item at the stop index is not extracted. This is so that the statement, seq[:i] + seq[i:] == seq
is always true. This is a common source of confusion. It can be easier if you think of slice indicies as pointing between the items, as in Fig. 31:
With positive start:stop
values, the size of the sub-sequence is the difference between the start
and stop
values.
A step
or stride value can be specified, to skip over items:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[0:5:2]
['a', 'c', 'e']
Omitting a start value:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[:3]
['a', 'b', 'c']
>>> foo[:3:2]
['a', 'c']
Omitting a stop value:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[0:]
['a', 'b', 'c', 'd', 'e']
>>> foo[0::2]
['a', 'c', 'e']
>>> foo[-4:]
['b', 'c', 'd', 'e']
Omitting a start and stop value:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[:]
['a', 'b', 'c', 'd', 'e']
>>> foo[::2]
['a', 'c', 'e']
Tip
The use of, [:]
is a quick way to shallow copy an entire mutable sequence.
Unlike what happens with an out of range index, if you specify an out of range slice, Python will handle it gracefully. For example:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> foo[0:100]
['a', 'b', 'c', 'd', 'e']
>>> foo[99:100]
[]
Finally, every slice of a mutable object creates a new object in Python. For example:
>>> foo = ['a', 'b', 'c', 'd', 'e']
>>> id(foo[0:0])
140073989998280
>>> id(foo[0:0])
140073990030088
Try it!
Given the following iterable:
“I own the worlds worst thesaurus. Not only is it awful, it’s awful.”
Perform the following:
- Extract the first character of the string.
- Extract the last character of the string (don’t count it first).
- Extract the word, “thesaurus” from the string.
- Reverse the word, “thesaurus”.
Sequences can be compared to each other if they are the same type and the same length. To do the comparison, each pair of elements is checked in turn against the comparison operation. For example, comparing for equality using the ==
operator:
>>> foo = [1, 2, 3]
>>> bar = [1, 2, 3]
>>> baz = [4, 5, 6]
>>> bif = [1, 2]
>>> pam = (1, 2, 3)
>>> foo == bar
True
>>> foo == baz
False
>>> foo == bif
False
>>> foo == pam
False
>>> range(0, 10, 2) == range(0, 9, 2)
True
Sequences also support comparing using <
, <=
, >
and >=
. For example:
>>> 'a' < 'b'
True
>>> 'a' <= 'b'
True
>>> 'a' > 'b'
False
>>> [1, 2] < [1, 2]
False
>>> [1, 2] <= [1, 2]
True
>>> [1, 2] <= [1, 4]
True
Strings compare based on their unicode code point.
Ranges can only be checked for equality.
Finally, the operations shown in Table 3 are available to all sequence types, immutable or mutable.
Operation | Description |
---|---|
x in seq | Membership testing. Returns >>> foo = (1, 'a', 2, 'b', 3, 'c')
>>> 'a' in foo
True
>>> bar = 'trampoline'
>>> 'line' in bar
True
|
x not in seq | Membership testing. Returns >>> foo = (1, 'a', 2, 'b', 3, 'c')
>>> '999' not in foo
True
|
len(seq) | Returns the number of items in seq. >>> foo = (1, 'a', 2, 'b', 3, 'c')
>>> len(foo)
6
>>> len(range(0, 10, 2))
5
|
min(seq) | Returns the minimum value found in seq. >>> foo = (100, 3, 40, 831, 16398)
>>> min(foo)
3
|
max(seq) | Returns the maximum value found in seq. >>> foo = (100, 3, 40, 831, 16398)
>>> max(foo)
16398
|
seq.index(x, [i, [j]]) | Return the index of the first occurrence of x in seq. Optionally start after index i and before index j. >>> foo = (100, 3, 40, 831, 16398)
>>> foo.index(40)
2
|
seq.count(x) | Returns the number of times x occurs in seq. >>> foo = (1, 2, 3, 2, 3)
>>> foo.count(2)
2
|
seq * n n * seq |
Repeat seq, n times. >>> foo = (1, 2, 3)
>>> foo * 2
(1, 2, 3, 1, 2, 3)
>>> '-' * 10
'----------'
Not supported by the |
seq0 + seq1 | Concatenate seq1 onto the end of seq0 >>> foo = (1, 2, 3)
>>> bar = (4, 5, 6)
>>> foo + bar
(1, 2, 3, 4, 5, 6)
>>> 'Hello' + 'World'
'HelloWorld'
Not supported by the |
Try it!
Given the following sequences:
“asdabafw”
[1, 4, 3, 8, 5]
Perform the following:
- Determine the length of each sequence.
- Determine if each sequence contains the string, “ab”
- Determine the min value in each sequence. Is it what you expected?
- Determine the max value in each sequence. Is it what you expected?
- Determine how many times “a” occurs in each sequence.
- Create a new sequence by repeating each sequence 4 times.
Mutable Sequence Operations¶
Mutable sequence types support additional operations that immutable sequences do not. These are shown in Table 4.
Operation | Description |
---|---|
seq[i] = x | Sets the i’th item in seq to x. >>> foo = [1, 2, 3]
>>> foo[1] = 99
>>> foo
[1, 99, 3]
|
seq0[i:j] = seq1 seq0[i:j:k] = seq1 |
Sets the slice i:j (or i:j:k) to seq1 >>> foo = [1, 2, 3, 4, 5, 6]
>>> foo[1:4] = [77, 88, 99]
>>> foo
[1, 77, 88, 99, 5, 6]
>>> foo = [1, 2, 3, 4, 5, 6]
>>> foo[1:6:2] = [77, 88, 99]
>>> foo
[1, 77, 3, 88, 5, 99]
|
del seq[i:j] del seq[i:j:k] |
Removes the slice i:j (or i:j:k) of elements from seq. >>> foo = [1, 2, 3, 4, 5, 6]
>>> del foo[1:4]
>>> foo
[1, 5, 6]
>>> foo = [1, 2, 3, 4, 5, 6]
>>> del foo[1:6:2]
>>> foo
[1, 3, 5]
|
seq.append(x) | Appends x to the end of seq. >>> foo = [1, 2, 3]
>>> foo.append(4)
>>> foo
[1, 2, 3, 4]
>>> foo.append([5, 6])
>>> foo
[1, 2, 3, 4, [5, 6]]
|
seq0.extend(seq1) seq0 += seq1 |
Extends seq0 with the items from seq1. >>> foo = [1, 2, 3]
>>> foo.extend([4])
>>> foo
[1, 2, 3, 4]
>>> foo.extend([5, 6])
>>> foo
[1, 2, 3, 4, 5, 6]
|
seq *= n | Assigns n repeats of seq, to seq. >>> foo = [1, 2, 3]
>>> foo *= 3
>>> foo
[1, 2, 3, 1, 2, 3, 1, 2, 3]
|
seq.insert(i, x) | Inserts x in seq at the index given by i. >>> foo = [1, 2, 3]
>>> foo.insert(1, 99)
>>> foo
[1, 99, 2, 3]
|
seq.pop([i]) | Removes and returns the i’th item from seq. >>> foo = [1, 2, 3, 4]
>>> foo.pop()
4
>>> foo.pop(1)
2
>>> foo
[1, 3]
|
seq.remove(x) | Removes the first element in seq that equals x. >>> foo = [1, 2, 4, 3, 3, 4]
>>> foo.remove(3)
>>> foo
[1, 2, 4, 3, 4]
>>> foo.remove(4)
[1, 2, 3, 4]
|
seq.reverse() | Reverses seq, in place. >>> foo = [1, 2, 3]
>>> foo.reverse()
>>> foo
[3, 2, 1]
|
seq.clear() | Removes all items from seq. >>> foo = [1, 2, 3]
>>> foo.clear()
>>> foo
[]
|
seq1 = seq0.copy() | Create a shallow copy of seq0. >>> foo = [1, 2, 3]
>>> id(foo)
140074018889672
>>> bar = foo.copy()
>>> id(bar)
140073989975880
>>> bar
[1, 2, 3]
|
Try it!
Given the following sequence:
[1, 4, 3, 8, 5, 7, 11]
Perform the following:
- Replace the value 8, with 100.
- Replace the sub-sequence of values, 1 4 3, with the values, 2 5 9.
- Extract the last value in the sequence into a variable. What does the sequence look like now?
- Tack the individual values in the sequence, [55, 66, 77], onto the end of the sequence.
- Tack the sequence, [88, 99], onto the end of the sequence as a single atomic value.
- Reverse the sequence.
Other Sequence Operations¶
While not defined specifically for sequences, Python defines some useful functions for types that can be iterated over, of which sequences are one such type.
Operation | Description |
---|---|
all(seq) | Returns >>> foo = [0, 1, 2, 3]
>>> all(foo)
False
>>> foo = [1, 1, 2, 3]
>>> all(foo)
True
|
any(seq) | Returns >>> foo = []
>>> any(foo)
False
>>> foo = [0, 0, 0]
>>> any(foo)
False
>>> foo = [0, 1, 0]
>>> any(foo)
True
|
sum(seq, [start]) | Sums start and all items in seq. >>> foo = [1, 2, 3]
>>> sum(foo)
6
Better to use Better to use |
sorted(seq[, key][, rev]) | Returns a list made up of seq sorted. Like >>> foo = (1, 35, 13, 52, 62, 4)
>>> sorted(foo)
[1, 4, 13, 35, 52, 62]
|
enumerate(seq, [start]) | Create enumerated tuples from seq. >>> foo = ['Jan', 'Feb', 'Mar', 'Apr']
>>> list(enumerate(foo))
[(0, 'Jan'), (1, 'Feb'), (2, 'Mar'), (3, 'Apr')]
|
zip(*seqs) | Aggregate all sequences into tuples by element. >>> foo = [1, 2, 3]
>>> bar = ['a', 'b', 'c']
>>> baz = [5.5, 12.7, 99.143]
>>> list(zip(foo, bar, vaz))
[(1, 'a', 5.5), (2, 'b', 12.7), (3, 'c', 99.143)]
|
Try it!
Given the following sequence:
(135, 2753, 9285)
Perform the following:
- Sum up all the values in the sequence.
- Sort the values in the sequence.
- Enumerate the values in the sequence.
Sequence Copying¶
Recall, =
only creates a reference and assigns it to a name.
- If the right side is an object, a new object reference is created.
- If the right side is an object reference, that object reference is used directly.
Thus, copying is cheap and fast and independent of object size. However, it means both names refer to the same object:
>>> foo = [1, ['a', 'b']]
>>> bar = foo
>>> bar[0] = 99
>>> bar[1][0] = 'Z'
>>> bar
[99, ['Z', 'b']]
>>> foo
[99, ['Z', 'b']]
It is possible to shallow-copy the sequence; that is to create copies of the directly referenced items, but not of any nested collections. To create a shallow copy, you can:
- Take a slice of the sequence
- Pass the sequence to the constructor of the same type
- Use
copy.copy()
An example that take a slice of the original object:
>>> foo = [1, ['a', 'b']]
>>> bar = foo[:] # Take slice of original object
>>> bar[0] = 99
>>> bar[1][0] = 'Z' # Nested collection is still shared
>>> bar
[99, ['Z', 'b']]
>>> foo
[1, ['Z', 'b']] # Nested collection still affected
It is also possible to deep-copy the sequence; that is to create copies of all directly referenced items and nested collections. For this, use copy.deepcopy()
. For example:
>>> import copy
>>> foo = [1, ['a', 'b']]
>>> bar = copy.deepcopy(foo) # Deep-copy original object
>>> bar[0] = 99
>>> bar[1][0] = 'Z'
>>> bar
[99, ['Z', 'b']]
>>> foo
[1, ['a', 'b']] # Original object untouched
Try it!
Work through the examples given above to make sure you understand what is going on.