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:

../../_images/slicing0.png

Fig. 30 Center Aligned Slicing Notation

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 positive step, otherwise -1
  • end 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:

../../_images/slicing1.png

Fig. 31 Edge Aligned Slicing Notation

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.

Table 3 Table of Common Sequence Operations
Operation Description
x in seq

Membership testing. Returns True if x is in seq.

>>> foo = (1, 'a', 2, 'b', 3, 'c')
>>> 'a' in foo
True
>>> bar = 'trampoline'
>>> 'line' in bar
True
x not in seq

Membership testing. Returns True if x is not in seq.

>>> 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 range class.

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 range class.

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.

Table 4 Table of Mutable Sequence Operations
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.

Table 5 Table of Other Sequence Operations
Operation Description
all(seq)

Returns True if all items in seq are true, or seq is empty.

>>> foo = [0, 1, 2, 3]
>>> all(foo)
False
>>> foo = [1, 1, 2, 3]
>>> all(foo)
True
any(seq)

Returns True if any items in seq are true and seq not empty.

>>> 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 + or str.join() for strings.

Better to use math.fsum() for floats.

sorted(seq[, key][, rev])

Returns a list made up of seq sorted.

Like list.sort() but for non-lists.

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