Let us return to the problem of recording the number of runs scored in every ball of an IPL match. A typical innings of a T20 match has 20 overs, each over having 6 balls. Let us assume that all balls bowled are fair deliveries that do not concede any extras, a rather liberal assumption. This leaves us with exactly 120 numbers that we need to record, all lying between 0 and 6. How can this information be stored in a Python program that makes it suitable for further processing? A list is a good candidate.
Let us now simulate an innings. For this, we take the help of the random
library:
xxxxxxxxxx
41import random
2runs = random.choices([0, 1, 2, 3, 4, 5, 6], k = 120)
3print(type(runs))
4print(len(runs))
choices
is a function in the random
library. It uniformly samples from the seven numbers (0 to 6) given in the input list with replacement. If that sounded too cryptic, this is what it does:
[0, 1, 2, 3, 4, 5, 6]
at random. Each of the seven numbers is equally likely to be picked.Let us verify if the counts are approximately the same:
xxxxxxxxxx
21for run in [0, 1, 2, 3, 4, 5, 6]:
2 print('{} appears {} times'.format(run, runs.count(run)))
runs.count(run)
returns the number of times the element run
appears in the list runs
. count
is a method defined for the list
type. This gives the following output:
xxxxxxxxxx
710 appears 19 times
21 appears 20 times
32 appears 19 times
43 appears 16 times
54 appears 18 times
65 appears 11 times
76 appears 17 times
The counts are quite close. But this is not very practical:
5
runs are seldom observed in cricket matches.0
, 1
and 2
are much more common than 3
, 4
and 6
.We can give our preferences using a weights keyword-argument:
xxxxxxxxxx
91import random
2# choices is distributed over multiple lines
3# this is done to improve readability
4runs = random.choices([0, 1, 2, 3, 4, 5, 6],
5 weights = [30, 30, 20, 5, 10, 0, 5],
6 k = 120)
7for run in [0, 1, 2, 3, 4, 5, 6]:
8 print('{} appears {} times'.format(run, runs.count(run)))
9print(f'Total number of runs scored = {sum(runs)}')
This gives the following output:
xxxxxxxxxx
810 appears 32 times
21 appears 34 times
32 appears 32 times
43 appears 7 times
54 appears 12 times
65 appears 0 times
76 appears 3 times
8Total number of runs scored = 185
We have used sum(runs)
to get the sum of the elements in the list. sum
is a built-in function. The way to understand the weights
keyword-argument is using the following table:
Run | Weight |
---|---|
0 | 30 |
1 | 30 |
2 | 20 |
3 | 5 |
4 | 10 |
5 | 0 |
6 | 5 |
Total | 100 |
The weight is the importance given to a run. From the table given above, we see that 0 and 1 occur 30% of the times, 6 occurs 5% of the times and so on. choices
function will keep this distribution in mind while picking up items from the input-list.
Let us now start analyzing this innings. We have already seen how to count the number of occurrences of singles, doubles, fours and sixes. What about the first occurrence of a six? In which ball was the first six scored?
xxxxxxxxxx
21first_six_ball = runs.index(6) + 1
2print(first_six_ball)
index
is a method that accepts an element as input and returns the first occurrence of this element in the list. For example, runs.index(6)
returns the first index where a six occurs in the list runs
. Since the number of balls is one more than the index, 1
has been added. What happens if we pass an input that is not present in the list:
xxxxxxxxxx
21first_five_ball = runs.index(5)
2print(first_five_ball)
In this case, 5
never occurs in the list. So this throws a ValueError
with the following message: 5 is not in list
. One must be careful while using the index
method. We could have done this using another method:
xxxxxxxxxx
41for ball, run in enumerate(runs):
2 if run == 6:
3 print(f'The first six was hit at ball number {ball + 1}')
4 break
The enumerate
object can be very handy when we want to access both the element and its index while iterating through a list. The enumerate object yields pairs: (index, list[index])
. In some sense, we have two loop variables: the first is the index of the element in the list while the second is the element itself. Coming back to cricket, what if we want to find the number of balls it took to score the last 50 runs in the innings? It would be easier to reverse the list and then iterate through it:
xxxxxxxxxx
81balls = 0
2last_runs = 0
3for run in reversed(runs):
4 last_runs += run
5 balls += 1
6 if last_runs >= 50:
7 print(f'It took {balls} balls to score the last 50 runs.')
8 break
The reversed
object helps us iterate through the list in the reversed order. Note that it doesn't make any changes to the original list. One final question: we wish to find if the batsmen have run three runs at any point in the match. We don't want to know at which point in the innings this has happened.
xxxxxxxxxx
21three_existence = 3 in runs
2print(three_existence)
Recall that we used the in
keyword to check for the presence of one string in another. Something similar is happening here. The code given above prints True
if 3 is an element in runs
and False
otherwise.