Remove every occurence of the number 13 and the next number from a list, then sum the result This function takes a list, deletes every occurrence of the number 13 and deletes the number present after 13 in every instance and then prints the sum of the list.  

How can I shorten it?

def average(nums):
  thirteen_idx = []
  bad_idx = []
  final_list = []
  num_length = len(nums)-1

  #list of indexes where 13 resides
  for i in range(len(nums)):
    if nums[i] == 13:
      thirteen_idx.append(i)

  #make list of indexes where 13 resides and the index after it (bad_idx)
  for i in range(len(thirteen_idx)):
    bad_idx.append(thirteen_idx[i])
    bad_idx.append(thirteen_idx[i]+1)

  #delete any index from bad_idx that is greater than the highest index in nums
    if bad_idx[-1] &gt; num_length:
        bad_idx.pop(len(bad_idx)-1)

  #we now have a list of all bad indexes (bad_idx)

  #iterate thru nums and move any non bad index into a final_list
  for i in range(len(nums)):
    if i not in bad_idx:
      final_list.append(nums[i])

  print (sum(final_list))
Your output is simply a sum of certain numbers. You don't need to build intermediary lists at all here, just select the numbers you want to sum, by looping. Your criteria are that the current number should not be 13, and the preceding number should not be 13.

To achieve this, you could just use a loop that remembers the preceding number:


initialize a preceding variable to a value you can easily test for. Here, any value other than 13 would do, and None is probably a good sentinel value.
In a loop over the numbers, if the current number is not 13 and the preceding number is not 13, add it to the running total.
At the end of the loop, set preceding to the current number. That way, when you go back to the top of the loop body, preceding will still contain the number from the preceding iteration.


So, given an input of [17, 13, 3, 9], you'd want to sum the first and the last number. We start with preceding set to None, result is set to 0 and we iterate 4 times:


preceding = None, set current to the first value 17.


preceding != 13 and current != 13 is true, so add the current value to the result -> result = 0 + 17 = 17.
set preceding = current, so 17

preceding = 17, set current to the second value 13.


preceding != 13 and current != 13 is false (current is set to 13), so skip this value.
set preceding = current, so 13

preceding = 13, set current to the third value 3.


preceding != 13 and current != 13 is false (preceding is set to 13), so skip this value.
set preceding = current, so 3

preceding = 3, set current to the forth value 9.


preceding != 13 and current != 13 is true, so add the current value to the result -> result = 17 + 9 = 26.
set preceding = current, so 9



and you end up with result = 26.

Generally speaking, when you find yourself wanting to look ahead in a for loop, turn the problem around and look behind instead. It is much simpler, as your loop will already have processed the items behind.

Translating those steps above to Python code looks like this:

def sum_without_13s(nums):
    """Sum all values that are not equal to 13 or directly follow 13"""

    # the running total of qualifying numbers
    result = 0
    # the preceding value in the loop, set at the end of the loop body
    preceding = None  # any value that is not 13 would do

    for value in nums:
        # only add to the sum if this value and the preceding value are both
        # *not* equal to 13.
        if value != 13 and preceding != 13:
            result += value

        # for the next iteration, remember the current value so we can
        # skip any values that followed 13.
        preceding = value

    return result


That's the most readable method of implementing your algorithm. The function produces the sum of integers not 13, and not directly following 13:

&gt;&gt;&gt; sum_without_13s([13, 99, 3])  # only 3 fits criteria, so should produce 3
3
&gt;&gt;&gt; sum_without_13s([99, 3])  # no 13s, so sum should be 102
102
&gt;&gt;&gt; sum_without_13s([1, 2, 13, 13, 3, 4])  # only 1, 2, and 4 should be summed == 7
7


Note that the function also returns the result, rather than print the result to the console. If you want to see the output of your function, you can always print the returned value:

&gt;&gt;&gt; theanswer = sum_without_13s([17, 13, 19, 18, 13, 13, 3, 7])
&gt;&gt;&gt; print(theanswer)
42


Other looping constructs that give you access to the preceding value

You could in principle make use of itertools.chain() and zip() to produce an iterator of (preceding, current) tuples to loop over, and so make it possible to filter and sum in a generator expression.

The above 'preceding value' approach is a variant of the pairwise() function from the itertools recipes section. But where pairwise() starts with (nums[0], nums[1]) and ends at (nums[-2], nums[-1]),  we really need to be starting with (None, nums[0]) first because we wouldn't want to skip either the first or the last value in your input from consideration.

So here's a with_preceding() function, with a configurable default value for that first element of the first tuple:

from itertools import chain, tee

def with_preceding(iterable, default=None):
    "s -&gt; (default, s0), (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    return zip(chain((default,), a), b)


which then can be used in an alternate implementation using a generator expression, like this:

def sum_without_13s_iter(nums):
    """Sum all values that are not equal to 13 or directly follow 13"""
    return sum(
        current
        for preceding, current in with_preceding(nums)
        if preceding != 13 and current != 13
    )


However, readability suffers, in my eyes, and for little gain as the above is actually slower than the first version. See the small benchmark at the very end.

Performance considerations

I tried to construct what I hoped would be a faster function using iterators, the itertools and operator modules and the sum(), map() and zip() functions to try and avoid the Python interpreter loop altogether, but this was simply not any faster; at best I could get it to be about 5-10% slower than the above first version of the function.

If performance is a concern, then you should really be using numpy arrays anyway. With a numpy array of integers as the input, the function can be expressed as:

import numpy as np

def array_sum_without_13s(numarray):
    """Sum all values in an array that are not equal to 13 or directly follow 13"""
    # boolean array with True where the input array has 13
    is13 = numarray == 13
    # boolean array with True where the input array is preceded by 13
    follows13 = np.concatenate(([False], is13[:-1]))
    # sum of all values that don't fit either of the above criteria
    return np.sum(numarray[~(is13 | follows13)])


As stated, this function expects a numpy array as the input:

&gt;&gt;&gt; import numpy as np
&gt;&gt;&gt; array_sum_without_13s(np.array([13, 99, 3]))
3
&gt;&gt;&gt; array_sum_without_13s(np.array([99, 3]))
102
&gt;&gt;&gt; array_sum_without_13s(np.array([1, 2, 13, 13, 3, 4]))
7
&gt;&gt;&gt; array_sum_without_13s(np.array([17, 13, 19, 18, 13, 13, 3, 7]))
42


Given an input of 1 million integers, the numpy version takes ~3.5 milliseconds versus the pure-Python version taking ~89 milliseconds. Numpy is the numeric processing champion, hands down:

In [1]: import random
   ...: import numpy as np
   ...: from codereview237899 import sum_without_13s, array_sum_without_13s
   ...: testdata = random.choices(range(1_000), k=1_000_000)
   ...: testarray = np.array(testdata)
   ...:

In [2]: %timeit sum_without_13s(testdata)
88.7 ms ± 2.12 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

In [3]: %timeit array_sum_without_13s(testarray)
3.55 ms ± 81.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

In [4]: %timeit sum_without_13s_iter(testdata)
119 ms ± 13.5 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)


The last test there is for the with_preceding() and generator expression variant above; it's a disappointing 35% slower.