Reputation: 29
I am writing a function batting_stats to show the batting statistics for each innings throughout a batsman's career. The input to the function is a list of lists of integers. The top level list contains a list of innings while the inner lists contain the runs scored, balls faced, and a Boolean (1=out or 0=not out) indicating whether or not the batsman was dismissed during the innings. The function is to return a list of lists of integers representing the batsman's average, strike rate and conversion rate for each of these innings.
Average = runs // dismissals (If the player is not dismissed, the average is the total runs scored)
Strike Rate = 100*runs // balls faced
Conversion Rate = 100*number of centuries scored/number of 50+ scores. (If the player has no scores greater than fifty then the conversion rate is zero)
Input Format:
[[r1,b1,d1],[r2,b2,d2],...]
where r=runs, b=balls, d=dissmissal
Output Format:
[[avg1,sr1,cr1],[avg2,sr2,cr2],...]
where avg=average, sr=strike rate, cr=conversion rate
For example:
>>> batting_stats([[12,24,0],[18,36,1]])
[[12,50,0],[30,50,0]]
My code is giving me the expected results but apparently the implementation is not optimal. I'm getting time out errors for very large inputs. How can I optimize it?
def batting_stats(lst):
"""Compute the average, strike rate, and conversion rate of a batsman after each innings."""
innings = len(lst) # number of innings
last = 1 + innings
r_lst = [r[0] for r in lst] # list of runs per innings
b_lst = [b[1] for b in lst] # list of balls faced per innings
d_lst = [d[2] for d in lst] # list of dismissals per innings
c_lst = [1 if r >= 100 else 0 for r in r_lst] # list of 100+ scores
f_lst = [1 if r >= 50 else 0 for r in r_lst] # list of 50+ scores
# Keep track of sums after each innings
rt = [sum(r_lst[:n]) for n in range(1, last)] # runs scored
bt = [sum(b_lst[:n]) for n in range(1, last)] # balls faced
dt = [sum(d_lst[:n]) for n in range(1, last)] # dismissals
ct = [sum(c_lst[:n]) for n in range(1, last)] # 100+ scores
ft = [sum(f_lst[:n]) for n in range(1, last)] # 50+ scores
avg_ = [rt[i] if dt[i] == 0 else rt[i] // dt[i] for i in range(innings)] # averages after each innings
sr_ = [100 * rt[i] // bt[i] for i in range(innings)] # strike rates after each innings
cr_ = [0 if ft[i] == 0 else 100 * ct[i] // ft[i] for i in range(innings)] # conversion rates after each innings
return [[avg_[i], sr_[i], cr_[i]] for i in range(innings)]
Upvotes: 0
Views: 596
Reputation: 1212
I used a combination of Daniel Mesejo's suggestion (using itertools.accumulate
) and unzip
from the more_itertools
module to modify your function (pardon the reformatting/renaming - it was for my own readability). I also made some pretty liberal use of zip
.
def batting_stats2(lst):
"""Compute the average, strike rate, and conversion rate of a batsman after each innings."""
innings = len(lst) # number of innings
# Not needed
#last = 1 + innings
# Unzip reshapes the various stats into their own lists
r_lst, b_lst, d_lst = map(list, unzip(lst))
# Similarly, for the 100+ and 50+ scores
# Note: int(True) = 1, int(False) = 0
c_lst, f_lst = map(list, unzip((int(r >= 100), int(r >= 50)) for r in r_lst))
# Accumulate the sums
rt = list(accumulate(r_lst)) # list of runs per innings
bt = list(accumulate(b_lst)) # list of balls faced per innings
dt = list(accumulate(d_lst)) # list of dismissals per innings
ct = list(accumulate(c_lst)) # list of 100+ scores
ft = list(accumulate(f_lst)) # list of 50+ scores
# averages after each innings
avg_ = [run if dismiss == 0 else run // dismiss for run, dismiss in zip(rt, dt)]
# strike rates after each innings
sr_ = [100 * run // ball for run, ball in zip(rt, bt)]
# conversion rates after each innings
cr_ = [fifty if fifty == 0 else 100 * hundo // fifty for fifty, hundo in zip(ft, ct)]
# The "list(x)" is because your output is nested lists. Without, it would be
# list of tuples.
return list(list(x) for x in zip(avg_, sr_, cr_))
I then tested with your posted example, and got the same output so it appears to be calculating correctly.
Afterwards, I timed it with an input of 1000000 3-member lists (called stats
):
>>> %%timeit
>>> batting_stats2(stats)
2.43 s ± 35.3 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
I also tried to time the original, but gave up after waiting for 10 minutes :)
Upvotes: 2