-1

Is there a way to check if a list is sorted either in ascending or descending order in one pass?

From this answer, I modified to also support descending order check, so my code now looks like this, is there a way to check this without having to use two for loops?

def is_sorted(check):
    current = check
    forwarded = check[1:]
    ascending_check = all(i<=j for i, j in zip(current, forwarded))
    descending_check = all(i>=j for i, j in zip(current, forwarded))
    return ascending_check or descending_check

numbers = list(range(1, 10))
print(is_sorted(numbers))
reverse_numbers = list(range(10, 0, -1))
print(is_sorted(reverse_numbers))
not_sorted = [1, 3, 2, 4, 5]
print(is_sorted(not_sorted))
Lorena
  • 75
  • 1
  • 7
  • The for-loop is running in O(n) so two for-loop is running in O(2 * n) which is not really that bad as you are still having linear time-complexity. – Quizzarex Feb 02 '21 at 08:04
  • @Quizzarex thanks for replying, I submitted this and I did not get the total marks, the reason was there is solution where only one for loop is needed – Lorena Feb 02 '21 at 08:09
  • Check out this answer - https://stackoverflow.com/a/3755251/2570277 – Nick Feb 02 '21 at 08:12
  • @Nick maybe I am not picking this up, but aren't I doing the same thing? `l[i] <= l[i+1]` would only check ascending so I still have to check descending don't I? – Lorena Feb 02 '21 at 08:15
  • @Lorena It is only testing ascending order – Quizzarex Feb 02 '21 at 08:21

4 Answers4

2

You can examine the first two elements, then return True if the entire list has the same ordering as those two. You have to also take into account the possibility that there is a tie at the beginning, and that the entire list only contains the same values throughout.

def is_sorted(check):
    ascending = True
    descending = True
    previous = check[0]
    for item in check[1:]:
        if ascending and item < previous:
            ascending = False
        if descending and item > previous:
            descending = False
        if not ascending and not descending:
            return False
        previous = item
    return True

Demo: https://ideone.com/YjBB9T

tripleee
  • 175,061
  • 34
  • 275
  • 318
  • As it seems like the method `is_sorted` is just asking if the array is sorted in either ascending or descending order, then I would `return ascending or descending`. – Quizzarex Feb 02 '21 at 08:27
  • @Quizzarex Thanks, I had misread the OP's code slightly. – tripleee Feb 02 '21 at 08:29
2

This would work:

def is_sorted(check):
    asc = desc = True
    i, L = 0, len(check)
    while (i < (L-1)) and (asc or desc):
        if check[i] < check[i+1]:
            desc = False
        elif check[i] > check[i+1]:
            asc = False
        if not (asc or desc):
            break
        i += 1
    if asc and desc:
        return "Ascending and Descending"
    elif asc:
        return "Ascending"
    elif desc:
        return "Descending"
    else:
        return "Not ascending and not descending"

Tests:

print (is_sorted([1, 2, 3]))
print (is_sorted([3, 2, 1]))
print (is_sorted([1, 2, 3, 2, 1]))
print (is_sorted([1, 1, 1]))

Output:

Ascending
Descending
Not ascending and not descending
Ascending and Descending
fountainhead
  • 3,584
  • 1
  • 8
  • 17
1

You could check pairs of consecutive elements, keep track of whether the first pair that is not == is < or >, and check that all the other pairs have the same relation (if not equal):

def is_sorted(lst):
    asc = None
    for i in range(len(lst)-1):
        a, b = lst[i], lst[i+1]
        if a == b:
            continue
        if asc is None:
            asc = (a < b)
        if (a < b) != asc:
            return False
    return True

That's a bit verbose, but you could also make it shorter with next and all, using a shared zip iterator for both so the entire list is iterated only once.

def is_sorted(lst):
    pairs = zip(lst, lst[1:])
    asc = next((a < b for a, b in pairs if a != b), None)
    return asc is None or all((a < b) == asc for a, b in pairs if a != b)

However, this version (like your original) will create a copy of the input list with lst[1:], so that's not really just a single loop, either. To fix that, you could e.g. use itertools.tee or islice.

tobias_k
  • 81,265
  • 12
  • 120
  • 179
  • No need for `asc is None or `. – superb rain Feb 02 '21 at 18:43
  • @superbrain Hm, good point... the `all` would still work as it would be `all` on an empty iterator due to `if a != b`... still I'd rather leave it in, it's a bit clearer that way IMHO. – tobias_k Feb 02 '21 at 18:49
  • Maybe. Although for clarity it might also be beneficial to not almost-repeat the generator. [Fun version](https://tio.run/##bU5tSgMxEP2/p3j@SyBK1ypIsVfwAqWELDvRwHYSJ2mpXn6d1qKgHYbhwfua8tHeMi@fiszzSBGp@pql0Wim2uyqg86YYqxYwwQ8Y0DMguAUJMZnKiehg55Nv9papIiAmzUGe/YKtb0wwjSZXSiG6djMOc/hJTPZO@/p3Xv3XWJt1/F@N5Cc@qZUm5HAr2R6h36hbJHEzfw@eRErI3RQQP66feGge9tfifhjVAXndiE1ZaPVS4d7hweHx@3/D37E6pznLw). – superb rain Feb 02 '21 at 18:58
  • Could also dump the `(a < b ... if a != b)` generator into the `all_equal` [recipe](https://docs.python.org/3/library/itertools.html#itertools-recipes), although that one is surprisingly complicated :-) – superb rain Feb 02 '21 at 19:11
-1

You can try to check if sorted ascending way, if not: array[::-1], and check again. array[::-1] reverses the array.

Sondge
  • 72
  • 4
  • Doesn't this require two loops as well? – Quizzarex Feb 02 '21 at 08:12
  • this is still two pass, I was told a one pass solution exists – Lorena Feb 02 '21 at 08:14
  • It's 3 passes, not 2. The expression `array[::-1]` itself triggers a (reversed) copy operation, and creating that copy counts as one pass (the second pass). Then, you pass this (reversed) copy to your function, which makes one more pass (the third pass) – fountainhead Feb 02 '21 at 09:57