Clifton
Clifton

Reputation: 29

How do I make this function more efficient? (cricket statistics)

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

Answers (1)

b_c
b_c

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

Related Questions