Abstract¶
Composite data types, like tuple and lists, allow programmers to group multiple objects together efficiently. This notebook focuses on sequence types, where objects are ordered. Readers will learn how to construct sequences using enclosure and comprehension; how to access items using subscriptions and slicing; the concept of mutation; and Methods that operate on sequences, including those that cause mutations. Understanding these concepts helps in managing collections of data more effectively, leading to cleaner, more maintainable, and scalable code.
import random
from math import isqrt
from collections.abc import IterableThe history saving thread hit an unexpected error (OperationalError('attempt to write a readonly database')).History will not be written to the database.
%reload_ext divewidgets
if not input('Load JupyterAI? [Y/n]').lower()=='n':
%reload_ext jupyter_aiLoad JupyterAI? [Y/n]
Motivation¶
The following code calculates the average of two numbers:
def average_of_two(x0, x1):
return (x0 + x1) / 2
average_of_two(0, 1)0.5How to calculate the average of more numbers? For instance, the average of four numbers 1, 2, 3, 4 is:
average_of_two(average_of_two(0, 1), average_of_two(2, 3))1.5But what about 5 numbers 0, 1, 2, 3, 4?
average_of_two(average_of_two(average_of_two(0, 1), average_of_two(2, 3)), 4)2.75Repeatedly applying the function does not always work. It is also impossible to specify an arbitrary number of optional arguments:
def average(x0, x1=None, x2=None, x3=None, ...):
...What is needed is a composite data type (or container):
def average(*args):
return sum(args) / len(args)
average(0, 1, 2, 3, 4)2.0Recall that args is a tuple that can keep a variable number of items in a sequence.
In calculating the average, sum and len return the sum and length of an iterable. There are also other built-in functions that can apply to an iterable directly:
min, max, sorted, enumerate, reversed, zip, map, filter, sliceWe can do this for average as well:
def average(seq):
return sum(seq) / len(seq)
seq = range(100)
average(seq)49.5max?
seq = (0, 1, 2, 3, 4)
max(seq), max(*seq)(4, 4)Docstring:
max(iterable, *[, default=obj, key=func]) -> value
max(arg1, arg2, *args, *[, key=func]) -> value
With a single iterable argument, return its biggest item. The
default keyword-only argument specifies an object to return if
the provided iterable is empty.
With two or more arguments, return the largest argument.
Type: builtin_function_or_methodfrom collections.abc import Iterable
def average(*args, **kwargs):
### BEGIN SOLUTION
if len(args) == 1 and isinstance(args[0], Iterable):
args = args[0]
if not len(args):
return kwargs['default']
return sum(args) / len(args)
### END SOLUTION# tests
seq = (0, 1, 2, 3, 4)
assert average(seq) == 2 == average(*seq)
assert average([], default=0) == 0Construction¶
How to store a sequence of items?
We created objects of sequence types before:
stris used to store a sequence of characters, but the items are limited to characters.rangeis used to generate a sequence of numbers, but the numbers must form an arithmetic sequence.
In order to store items of possibly different types, we can use the built-in types tuple and list:
%%optlite -l -h 400
a_list = "1 2 3".split()
a_tuple = (lambda *args: args)(1, 2, 3)How to create a tuple/list?
Mathematicians often represent a collection of items in two different ways:
Roster notation, which enumerates the elements, e.g.,
Set-builder notation, which describes the content using a rule for constructing the elements, e.g.,
namely the set of perfect squares strictly less than 100, which is the same as (1). denotes the set of natural numbers (including 0).
Python also provides two corresponding ways to create a collection of items:
- Enclosure, which uses brackets to group elements together.
- Comprehension, which uses concise syntax similar to iterations and conditionals to generate elements.
%%ai -f math
Give a table summarizing the set-builder notations.%%ai -f math
List some mathmatical symbols use for common sets of numbers.Enclosure¶
For instance, to create a tuple, we enclose a comma separated sequence of values by parentheses:
%%optlite -h 450
empty_tuple = ()
singleton_tuple = (0,) # why not (0)?
heterogeneous_tuple = (
singleton_tuple, (1, 2.0),
print
)
enclosed_starred_tuple = (
*range(2),
*"23"
)Note from the above code that:
- 2nd assignment: If the enclosed sequence has one term, there must be a comma after the term.
- 3rd assignment: The elements of a tuple can have different types.
- 4th assignment: The unpacking operator
*can unpack an iterable into a sequence in an enclosure.
To create a list, we use square brackets instead of parentheses to enclose objects.
%%optlite -h 400
empty_list = []
singleton_list = [0] # no need to write [0,]
heterogeneous_list = [
singleton_list,
(1, 2.0),
print
]
enclosed_starred_list = [
*range(2),
*"23"
]We can also create a tuple/list from other iterables using the constructors tuple/list as well as addition and multiplication similar to str.
%%optlite -l -h 900
str2list = list("Hello")
str2tuple = tuple("Hello")
range2list = list(range(5))
range2tuple = tuple(range(5))
tuple2list = list((1, 2, 3))
list2tuple = tuple([1, 2, 3])
concatenated_tuple = (1,) + (2, 3)
concatenated_list = [1, 2] + [3]
duplicated_tuple = (1,) * 2
duplicated_list = 2 * [1]print((1 + 2) * 2, (1 + 2,) * 2, sep="\n")6
(3, 3)
Solution to Exercise 2
(1+2)*2 evaluates to 6 but (1+2,)*2 evaluates to (3,3).
- The parentheses in
(1+2)indicate the addition needs to be performed first, but - the parentheses in
(1+2,)creates a tuple.
Hence, singleton tuple must have a comma after the item to differentiate these two use cases.
%%ai
Explain in a paragraph why a singleton tuple must have a comma after the item?Comprehension¶
How to use a rule to construct a tuple/list?
We can define the rules for constructing a sequence using a comprehension, a technique we’ve previously applied in a generator expression. For example, the following Python one-liner returns a generator for prime numbers:
def prime_sequence(stop):
return (x for x in range(2, stop) if
all(x % d for d in range(2, isqrt(x) + 1)))
print(*prime_sequence(100))2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
There are two comprehensions used in the return value:
(x for x in range(2, stop) if ...): The comprehension creates a generator of numbers from 2 tostop-1that satisfy the condition of theifclause.(x % d for d in range(2, isqrt(x) + 1)): The comprehension creates a generator of remainders to the functionall, which returnsTrueif all the remainders are non-zero elseFalse.
def prime_sequence_(stop):
return filter(
lambda x: all(
map(lambda d: x % d, range(2, isqrt(x) + 1))
),
range(2, stop)
)
print(*prime_sequence(100))2 3 5 7 11 13 17 19 23 29 31 37 41 43 47 53 59 61 67 71 73 79 83 89 97
### BEGIN SOLUTION
def composite_sequence(stop):
return (x for x in range(2, stop) if any(x % d == 0 for d in range(2, x)))
### END SOLUTION
print(*composite_sequence(100))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
Comprehension can also be used to construct a list instead of a generator. An example of list comprehension is as follows:
def prime_list(stop):
return [
x for x in range(2, stop) if all([x % d for d in range(2, isqrt(x) + 1)])
] # Enclose comprehension by square brackets
print(prime_list(100))[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97]
Comprehension used in nbgrader for grading feedback
nbgrader for grading feedbackWhy Nbgrader->Assignment List may not show (feedback available to fetch) even after grading feedback is released?
The following is the relevant code of how nbgrader list assignments with grading feedback:
175 176 177 178if info['notebooks']: has_local_feedback = all([nb['has_local_feedback'] for nb in info['notebooks']]) has_exchange_feedback = all([nb['has_exchange_feedback'] for nb in info['notebooks']]) feedback_updated = any([nb['feedback_updated'] for nb in info['notebooks']])
(feedback available to fetch) only appears if has_exchange_feedback is true. This happens when all submitted notebooks (nb in info['notebooks']) have exchange feedback (nb['has_exchange_feedback'] is true). What if a student includes an unexpected notebook file in a submission or rename a notebook? See the patch we applied. (What is the fix and why?)
As a demonstration of list comprehension, consider simulating the coin tossing game:
With list comprehension, we can easily simulate a sequence of biased coin flips as follows:
from random import random as rand
p = 1302/10000 # unknown chance of head
coin_flips = ["H" if rand() <= p else "T" for i in range(1000000)]
print("Chance of head:", p)
print("Coin flips:", *coin_flips[:100], "...")Chance of head: 0.1302
Coin flips: T H T T T T H T T T T T T H T T T T T T T H H T T T H T T T T T T T T H T T T T T T H T T H T T T T T T T T T T T T H H H T T H T T T T H T T T T T T H T T T H T T T T T T T T T T T T H T T T T T T T ...
p should be kept secret, while coin_flips can be shown to the player:
Hmeans a head comes up, andTmeans a tail comes up.
How to estimate the chance p from coin_flips?
p from coin_flips?Given that there heads in coin flips, a simple estimate is the fractional count of heads observed:
head_indicators = [1 if outcome == "H" else 0 for outcome in coin_flips]
phat = sum(head_indicators) / len(head_indicators)
print("Fraction of heads observed:", phat)Fraction of heads observed: 0.130404
Does the estimate look reasonable. How accurate is this estimate?
Let’s formulate the problem mathematically. Denote the total number of coin flips by . For , define
which is called an indicator variable.
The estimate above can be expressed in terms of and ’s as follows:
namely, the sample average of ’s. (Why?) This is an example of an M-estimator.
The variation of the estimate can be calculated from the sample variance:[1]
def variance(seq):
### BEGIN SOLUTION
return (
(sum(i**2 for i in seq) / len(seq) - average(seq) ** 2)
)
### END SOLUTION
v = variance(head_indicators)
n = len(head_indicators)
delta = 2 * (v / n) ** 0.5
print(f"p \u2248 {phat:.4f} \u00B1 {delta:.4f} except for 5% chance.")
print(
"95% confidence interval estimate of p: [{:.4f},{:.4f}]".format(
phat - delta, phat + delta
)
)p ≈ 0.1304 ± 0.0007 except for 5% chance.
95% confidence interval estimate of p: [0.1297,0.1311]
There is a simpler way to calculate the variance for coin tosses, which follows a Bernoulli distribution:
v = phat * (1 - phat)
print(f"p \u2248 {phat:.4f} \u00B1 {2*(v/n)**0.5:.4f}")p ≈ 0.1304 ± 0.0007
%%ai -f math
Explain the formula for the variance of samples of Bernoulli variables.Operations¶
Selection¶
How to traverse a tuple/list?
We can use a for loop to iterate over all the items in order.
a = (*range(5),)
for item in a:
print(item, end=" ")0 1 2 3 4 To do it in reverse, we can use the reversed function.
reversed?
a = [*range(5)]
for item in reversed(a):
print(item, end=" ")4 3 2 1 0 Init signature: reversed(sequence, /)
Docstring: Return a reverse iterator over the values of the given sequence.
Type: type
Subclasses: We can also traverse multiple tuples/lists simultaneously by zipping them.
zip?
a = (*range(5),)
b = reversed(a)
for item1, item2 in zip(a, b):
print(item1, item2)0 4
1 3
2 2
3 1
4 0
Init signature: zip(self, /, *args, **kwargs)
Docstring:
zip(*iterables, strict=False) --> Yield tuples until an input is exhausted.
>>> list(zip('abcdefg', range(3), range(4)))
[('a', 0, 0), ('b', 1, 1), ('c', 2, 2)]
The zip object yields n-length tuples, where n is the number of iterables
passed as positional arguments to zip(). The i-th element in every tuple
comes from the i-th iterable argument to zip(). This continues until the
shortest argument is exhausted.
If strict is true and one of the arguments is exhausted before the others,
raise a ValueError.
Type: type
Subclasses: How to select an item in a sequence?
We can select an item of a sequence a by subscription
a[i]where a is a list and i is an integer index.
A non-negative index indicates the distance from the beginning.
%%optlite -h 500
a = (*range(10),)
print(a)
print("Length:", len(a))
print("First element:", a[0])
print("Second element:", a[1])
print("Last element:", a[len(a) - 1])
print(a[len(a)]) # IndexErrorA negative index represents a negative offset from an imaginary element one past the end of the sequence.
%%optlite -h 500
a = [*range(10)]
print(a)
print("Last element:", a[-1])
print("Second last element:", a[-2])
print("First element:", a[-len(a)])
print(a[-len(a) - 1]) # IndexErrorHow to select multiple items?
We can use slicing to select a range of items as follows:
a[start:stop]
a[start:stop:step]The selected items corresponds to those indexed using range:
(a[i] for i in range(start, stop))
(a[i] for i in range(start, stop, step))a = (*range(10),)
print(a[1:4])
print(a[1:4:2])(1, 2, 3)
(1, 3)
Unlike range, the parameters for slicing take their default values if missing or equal to None:
a = [*range(10)]
print(a[:4]) # start defaults to 0
print(a[1:]) # stop defaults to len(a)
print(a[1:4:]) # step defaults to 1[0, 1, 2, 3]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3]
The parameters can also take negative values:
print(a[-1:])
print(a[:-1])
print(a[::-1]) # What are the default values used here?[9]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
A mixture of negative and postive values are also okay:
print(a[-1:1]) # equal [a[-1], a[0]]?
print(a[1:-1]) # equal []?
print(a[1:-1:-1]) # equal [a[1], a[0]]?
print(a[-100:100]) # result in IndexError like subscription?[]
[1, 2, 3, 4, 5, 6, 7, 8]
[]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Can AI explain the rules for slicing?
%%ai
Explain very concisely in a paragraph or two how the default values of
start, stop, and step are determined in the slicing operations in Python:
```python
print(a[-1:1]) # equal [a[-1], a[0]]?
print(a[1:-1]) # equal []?
print(a[1:-1:-1]) # equal [a[1], a[0]]?
print(a[-100:100]) # result in IndexError like subscription?
```def sss(a, i=None, j=None, k=None):
### BEGIN SOLUTION
l = len(a)
step = 1 if k is None else k
m = l if step > 0 else l - 1
start = 0 if i is None else min(i if i > 0 else max(i + l, 0), m)
stop = l if j is None else min(j if j > 0 else max(j + l, 0), m)
### END SOLUTION
return start, stop, step
a = [*range(10)]
assert sss(a, -1, 1) == (9, 1, 1)
assert sss(a, 1, -1) == (1, 9, 1)
assert sss(a, 1, -1, -1) == (1, 9, -1)
assert sss(a, -100, 100) == (0, 10, 1)With slicing, we can now implement a practical sorting algorithm called quicksort to sort a sequence. Explain how the code works:
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")[45, 22, 10, 97, 85, 45, 98, 98, 91, 21]
[10, 21, 22, 45, 45, 85, 91, 97, 98, 98]
Solution to Exercise 6
The above recursion creates a sorted list as [*left, pivot, *right] where
pivotis a randomly selected item inseq,leftis the sorted list of items smaller thanpivot, andrightis the sorted list of items no smaller thanpivot.
The base case happens when seq contains at most one item, in which case seq is already sorted.
Quick sort is an example of randomized algorithm. In particular, the pivot is randomly chosen. Why?
%%ai
For the quicksort algorithm, explain in a paragraph whether it is okay to pick
the pivot deterministically, say the first element of the sequence?%%ai
Explain in a paragraph what randomized algorithm is and how randomization helps.%%ai
Explain briefly in a paragraph or two whether we can derandomize a randomized
algorithm without loss of efficiency. Is P=BPP?Computational Complexity Classes
Mutation¶
%%ai
Explain in a paragraph or two why one would prefer tuple over list in Python,
given that list is mutable but tuple is not?For list (but not tuple), subscription and slicing can also be used as the target of an assignment operation to mutate the list:
%%optlite -h 350
b = [*range(10)] # aliasing
b[::2] = b[:5]
b[0:1] = b[:5]
b[::2] = b[:5] # failsLast assignment fails because [::2] with step size not equal to 1 is an extended slice, which can only be assigned a list of equal size.
%%ai
Explain in a paragraph or two the following limitation of extended slice in
Python as compared to the basic slice:
When assigning to an extended slice, the list on the right hand side of the
statement must contain the same number of items as the slice it is replacing.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.
list.__setitem__?Signature: list.__setitem__(self, key, value, /)
Call signature: list.__setitem__(*args, **kwargs)
Type: wrapper_descriptor
String form: <slot wrapper '__setitem__' of 'list' objects>
Namespace: Python builtin
Docstring: Set self[key] to value.Explain why the check returns False.
# %%optlite -l -h 400
a = b = [0]
b[0] = a[0] + 1
print(a[0] < b[0])False
Solution to Exercise 7
- The first line
a = bmakesaan alias of the same objectbpoints to, and so - the second line mutates the same object
aandbpoint to. - Hence,
a[0] == b[0].
Explain why the mutations below have different effects?
a = [0, 1]
i = 0
a.__setitem__(i := i + 1, i)
print(a)
code1=In[-1].rsplit('\n',maxsplit=1)[0][0, 1]
a = [0, 1]
i = 0
a[i := i + 1] = a[i]
print(a)
code2=In[-1].rsplit('\n',maxsplit=1)[0][0, 0]
Solution to Exercise 8
a[i := i + 1] = a[i] is not the same as calling a.__setitem__(i := i + 1, i). According to the python documentation,
- the expression to be assigned, i.e.,
a[i], is first evaluated toa[0]and therefore0; - since the target
a[i := i + 1]is a user defined object, it continues to evaluate the target reference, i.e., the address ofa, which corresponds to the list[0, 1], - followed by the subscription
i:=i+1, which evaluates to1; - Finally,
a.__setitem__is called with the subscription,1, and expression to be assigned,0, and so the listapoints to is mutuated to[0, 1].
In comparison, directly calling a.__setitem__(i := i + 1, i)
- first evaluates the first argument
i := i + 1, which gives1that is assigned toi, and - then evaluates the second argument
ito1, and so a.__setitem__(1, 1)is called instead, which does not change the listapoints to.
Let’s see if AI has the correct understanding:
%%ai
Explain what gets printed when running the following Python code:
---
{code2}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])
sieve_code=In[-1].rsplit('\n', maxsplit=1)[0]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, and - returns a generator that generates composite numbers according to
is_composite.
%%ai
Explain in a paragraph or two why `factor ** 2` is used instead of `factor * 2`
in the following function that attempts to generates a sequence of composite
numbers up to and excluding stop.
---
{sieve_code}Is sieve_composite_sequence more efficient than your solution composite_sequence in Exercise 3? Why?
# A sample if you did not define composite_sequence before.
def composite_sequence(stop):
return (x for x in range(2, stop) if \
any(x % d == 0 for d in range(2, isqrt(x) + 1)))%%timeit
for x in composite_sequence(10000): pass11.3 ms ± 103 μs per loop (mean ± std. dev. of 7 runs, 100 loops each)
%%timeit
for x in sieve_composite_sequence(10000): pass1.17 ms ± 3 μs per loop (mean ± std. dev. of 7 runs, 1,000 loops each)
for x in sieve_composite_sequence(10000000): passSolution to Exercise 9
The line if is_composite[factor]: continue avoids the redundant computations of checking composite factors.
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]].
%%optlite -h 300
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]]
%%ai
Explain the different levels of copy for Python lists.Methods¶
There is also a built-in function sorted for sorting a sequence:
sorted?
sorted(seq)[10, 21, 22, 45, 45, 85, 91, 97, 98, 98]Signature: sorted(iterable, /, *, key=None, reverse=False)
Docstring:
Return a new list containing all items from the iterable in ascending order.
A custom key function can be supplied to customize the sort order, and the
reverse flag can be set to request the result in descending order.
Type: builtin_function_or_methodIs quicksort quicker?
%%timeit
quicksort(seq)7.77 μs ± 36.5 ns per loop (mean ± std. dev. of 7 runs, 100,000 loops each)
%%timeit
sorted(seq)196 ns ± 3.72 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
Timsort explained
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.
%%optlite -l -h 450
a = (1,2,2,4,5)
count_of_2 = a.count(2)
index_of_1st_2 = a.index(2)reverse method reverses the list instead of returning a reversed list.
%%optlite -h 300
a = [*range(10)]
print(reversed(a))
print(*reversed(a))
print(a.reverse())copymethod returns a shallow copy of a list.tupledoes not have thecopymethod but it is easy to create a copy by slicing.
%%optlite -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.
%%optlite -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.
%%optlite -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.
%%optlite -h 300
a = [*range(10)]
del a[::2]
print(a.pop())
print(a.remove(5))
print(a.clear())%%ai -f html
Summarize in a table with examples all the non-dunder methods of list in Python.%%ai -f html
Summarize in a table with examples other useful functions that can operate on
a list, such as map.If is small (fewer than 100), the unbiased sample variance should be used.