13

I have a function named checker(nums) that has an argument that will later receive a list. What i want to do with that list is to check if each other element is greater or equal to the previous one. Example: I have a list [1, 1, 2, 2, 3] and i have to check if it fulfills the condition. Since it does, the function should return True

My code:

def checker(nums):
    for x in range(len(nums)):
        if x+1<len(nums):
            if nums[x] <= nums[x+1] and nums[-1] >= nums[-2]:
                return True

This will only run once and return True if the first condition is true. I've seen a statement if all and am unsure of how to use it.

jpp
  • 159,742
  • 34
  • 281
  • 339
LaurentiuS
  • 304
  • 3
  • 10

3 Answers3

32

Your function can be reduced to this:

def checker(nums):
    return all(i <= j for i, j in zip(nums, nums[1:]))

Note the following:

  • zip loops through its arguments in parallel, i.e. nums[0] & nums[1] are retrieved, then nums[1] & nums[2] etc.
  • i <= j performs the actual comparison.
  • The generator expression denoted by parentheses () ensures that each value of the condition, i.e. True or False is extracted one at a time. This is called lazy evaluation.
  • all simply checks all the values are True. Again, this is lazy. If one of the values extracted lazily from the generator expression is False, it short-circuits and returns False.

Alternatives

To avoid the expense of building a list for the second argument of zip, you can use itertools.islice. This option is particularly useful when your input is an iterator, i.e. it cannot be sliced like a list.

from itertools import islice

def checker(nums):
    return all(i <= j for i, j in zip(nums, islice(nums, 1, None)))

Another iterator-friendly option is to use the itertools pairwise recipe, also available via 3rd party more_itertools.pairwise:

# from more_itertools import pairwise  # 3rd party library alternative
from itertools import tee

def pairwise(iterable):
    "s -> (s0,s1), (s1,s2), (s2, s3), ..."
    a, b = tee(iterable)
    next(b, None)
    return zip(a, b)

def checker(nums):
    return all(i <= j for i, j in pairwise(nums))

Another alternative is to use a functional approach instead of a comprehension:

from operator import le

def checker_functional(nums):
    return all(map(le, nums, nums[1:]))
jpp
  • 159,742
  • 34
  • 281
  • 339
9

Your code can in fact be reduced to checking if nums is sorted, e.g.

def checker(nums):
    return sorted(nums) == nums

This does exactly what you expect, e.g.

>>> checker([1, 1, 2, 2, 3])
True
>>> checker([1, 1, 2, 2, 1])
False
Jonas Adler
  • 10,365
  • 5
  • 46
  • 73
  • 3
    Downside is that this solution is O(N log N). Upside is simplicity – jamylak Feb 19 '18 at 15:38
  • This is only partially true. If the list is sorted, this is in fact only O(n), see e.g. https://stackoverflow.com/questions/23809785/python-sorting-complexity-on-sorted-list. But sure, the worst case complexity is O(n log n). – Jonas Adler Feb 19 '18 at 15:57
  • 1
    You're wrong, that's not "partially true", I only referred to Worst Case. I never once talked about best case or average case. If I was talking about best case I would have used the correct symbol. And the reason why we are doing this check is because sometimes the list is not sorted, therefore it is O(N log N) – jamylak Feb 20 '18 at 05:43
3

Similar solution to @jp_data_analysis using more_itertools.windowed

>>> from more_itertools import windowed
>>> nums = [1, 1, 2, 2, 3]
>>> all(i <= j for i, j in windowed(nums, 2))
True

And for scientific purposes (not recommended code), here is a more functional approach

>>> from operator import le
>>> from itertools import starmap
>>> all(starmap(le, windowed(nums, 2)))
True
jamylak
  • 128,818
  • 30
  • 231
  • 230