# Python address index +1 with list comprehension

Posted on

Problem

Return the sum of the numbers in the array, returning 0 for an empty array. Except the number 13 is very unlucky, so it does not count and numbers that come immediately after a 13 also do not count.

My original answer to the problem:

``````def sum13(nums):
sum = 0
for idx,val in enumerate(nums):
if val == 13 or (idx != 0 and nums[idx-1] == 13):
pass
else:
sum = sum + val

return sum
``````

Doing this with list comprehension, I came up with

``````return sum([x if x!=13 and nums[idx-1 if idx >0 else 0] !=13 else 0 for idx,x in enumerate(nums)])
``````

Is there a way to make this cleaner?

Solution

For the record, I think your original answer is quite clean and readable. My only suggestion would be to consider using `if not (predicate): (do something)`, as opposed to `if (predicate): pass; else: (do something)`:

``````def sum13(nums):
sum = 0
for idx,val in enumerate(nums):
if not (val == 13 or (idx != 0 and nums[idx-1] == 13)):
sum += val
return sum
``````

I like @Josay’s suggestion of iterating over pairs of consecutive items. The easiest way to do this is by `zip`ping the list with the list starting from index 1 — i.e., `zip(L, L[1:])`. From there, it’s just a matter of taking the second item of each pair, unless either of the items == 13. In order to consider the very first item in the list, we’ll prepend `0` onto the beginning of the list, so that the first pair is `[0, first-item]`. In other words, we are going to `zip` together `[0] + L` (the list with 0 prepended) and `L` (the list itself). Here are two slightly different versions of this approach:

``````def sum13(nums):
sum = 0
for first, second in zip([0] + nums, nums):
if not 13 in (first, second):
sum += second
return sum
``````

Version 2, a functional approach using a list comprehension:

``````def sum13(nums):
pairs = zip([0] + nums, nums)
allowed = lambda x, y: 13 not in (x, y) # not 13 or following a 13
return sum(y for x, y in pairs if allowed(x, y))
``````

EDIT: As pointed out by an anonymous user, my first version did not skip numbers that follow an even number of 13’s.

Use an iterator. While you `for` loop over an iterator you can skip items with `next`.

``````def lucky_nums(nums):
nums = iter(nums)
for i in nums:
if i == 13:
while next(nums) == 13:
pass
else:
yield i

print sum(lucky_nums([12,13,14,15]))
``````

It’s a little “unclean” checking the previous element each time. You can maintain the loop index yourself to avoid this:

``````def sum13(nums):
sum = i = 0
while i < len(nums):
if nums[i] == 13:
i += 2  # Exclude this element, and the next one too.
else:
sum += nums[i]
i += 1
return sum
``````

This is similar to the iterator/generator answer.

A few simple comments about your original code : you could rewrite `if A: pass else do_stuff()` without the `pass` just writing `if not A: do_stuff()`. In your case, using De Morgan’s laws, your code becomes :

``````def sum13(nums):
sum = 0
for idx,val in enumerate(nums):
if val != 13 and (idx == 0 or nums[idx-1] != 13):
sum = sum + val
return sum
``````

Please note that you have different ways of avoiding accessing the array using indices :

• Save previous item

For instance :

``````def sum13(nums):
sum = 0
prev = None # or any value different from 13
for val in nums:
if val != 13 and prev != 13:
sum = sum + val
prev = val
return sum
``````

Now, a quick comment about your new code : you are summin `x if condition else 0` to sum all values matching the condition. You could just use `if` in your list comprehension to filter out elements you don’t want.

``````def sum13(nums):
return sum([x if x!=13 and nums[idx-1 if idx >0 else 0] !=13 else 0 for idx,x in enumerate(nums)])
``````

becomes :

``````def sum13(nums):
return sum([x for idx,x in enumerate(nums) if x!=13 and nums[idx-1 if idx >0 else 0] !=13])
``````

Also, your code creates a temporary list which is not really required. You could simply write :

``````def sum13(nums):
return sum(x for idx,x in enumerate(nums) if x!=13 and nums[idx-1 if idx >0 else 0] !=13)
``````

Now it seems like an other answer has been given so I don’t have much to say.

Noting your initial response to the problem

``````def sum13(nums):
sum = 0
for idx,val in enumerate(nums):
if val == 13 or (idx != 0 and nums[idx-1] == 13):
pass
else:
sum = sum + val

return sum
``````

you really should write it like this

``````def sum13(nums):
sum = 0
for idx,val in enumerate(nums):
if not(val == 13 or (idx != 0 and nums[idx-1] == 13)):
sum = sum + val
return sum
``````

there is no reason to add an extra block to an if statement if you don’t have to, I know that a lot of people don’t like the negatives, but if it is writing a negative if statement or writing an empty if statement, you should write the negative if statement, in this case it is straight to the point