30

I want to extend Array class so that it can know whether it is sorted (ascending) or not. I want to add a computed property called isSorted. How can I state the elements of the Array to be comparable?

My current implementation in Playground

extension Array {
  var isSorted: Bool {
    for i in 1..self.count {
      if self[i-1] > self[i] { return false }
    }
    return true
  }
}

// The way I want to get the computed property
[1, 1, 2, 3, 4, 5, 6, 7, 8].isSorted //= true
[2, 1, 3, 8, 5, 6, 7, 4, 8].isSorted //= false

The error Could not find an overload for '>' that accepts the supplied arguments

Of course, I still got an error because Swift doesn't know how to compare the elements. How can I implement this extension in Swift? Or am I doing something wrong here?

9
  • possible duplicate of How to implement flatten as an extension on an Array without type casting? Commented Jul 7, 2014 at 3:31
  • 3
    You can't extend Array<Comparable>, but you can implement a function that operates on Array<Comparable>. Have a look at stackoverflow.com/a/24565627/1489997 Commented Jul 7, 2014 at 3:37
  • @Sebastian I think the link that you gave is quite different than my intention. It's pretty easy to make category for this kind of thing in obj-c, so I thought it should be as trivial in Swift. Commented Jul 7, 2014 at 13:45
  • @IkhsanAssaat this is not any easier in Objective C. You still have to find a way to compare elements, and ObjC doesn't give you a magical function to do that. I suggest you try writing such a function in Objective C, then maybe you'll understand why. Commented Jul 13, 2014 at 17:31
  • @Kevin: The problem is easier in Objective-C since an NSArray can only store objects, and you can ask them whether they implement a protocol or respond to a selector (compare:). Also, you can force casting in Objective-C. With Swift, the problem is harder as Swift doesn't allow you to "work around" the compiler. Also, there are @objc protocols and non-@objc protocols and you can only check whether a type conforms to the later, but Comparable is non-@objc. That's why let foo = (bar as Any) as? Comparable doesn't work: the compiler does not allow you to "trick" it. Commented Jul 13, 2014 at 17:54

13 Answers 13

35
+50

The alternative solution to a free function is to do what Swift's built-in Array.sort and Array.sorted methods do, and require that you pass a suitable comparator to the method:

extension Array {
    func isSorted(isOrderedBefore: (T, T) -> Bool) -> Bool {
        for i in 1..<self.count {
            if !isOrderedBefore(self[i-1], self[i]) {
                return false
            }
        }
        return true
    }
}

[1, 5, 3].isSorted(<) // false
[1, 5, 10].isSorted(<) // true
[3.5, 2.1, -5.4].isSorted(>) // true
Sign up to request clarification or add additional context in comments.

3 Comments

Note that this actually fails for the example provided in the question: [1, 1, 2, 3, 4, 5, 6, 7, 8].isSorted(<), because it doesn't handle repeated values.
I think you want to change if !isOrderedBefore(self[i-1], self[i]) to if isOrderedBefore(self[i], self[i-1]) to fix the problem pointed out by nschum.
Be careful, this solution would crash with empty arrays. I would add a guard checking that it isn't an empty array
23

In Swift 4.2 and later you can cobble together allSatisfy and zip with some sequence slicing:

extension Array where Element: Comparable {
    func isAscending() -> Bool {
        return zip(self, self.dropFirst()).allSatisfy(<=)
    }

    func isDescending() -> Bool {
        return zip(self, self.dropFirst()).allSatisfy(>=)
    }
}

Comments

20

In Swift 2.0 you can now extend protocols!

extension CollectionType where Generator.Element: Comparable {

    public var isSorted: Bool {

        var previousIndex = startIndex
        var currentIndex = startIndex.successor()

        while currentIndex != endIndex {

            if self[previousIndex] > self[currentIndex] {
                return false
            }

            previousIndex = currentIndex
            currentIndex = currentIndex.successor()
        }

        return true
    }

}

[1, 2, 3, 4].isSorted // true
["a", "b", "c", "e"].isSorted // true
["b", "a", "c", "e"].isSorted // false
[/* Anything not implementing `Comparable` */].isSorted // <~~ Type-error

Note that because we're using Indexable.Index instead of a simple Int as an index we have to use a while-loop instead, which looks a bit less pretty and clear.

1 Comment

And for Swift 3, Generator becomes Iterator and someIndex.successor() becomes self.index(after: someIndex)
7

Actually, you can extend the Sequence protocol for a more generic solution:

extension Sequence {
    func isSorted(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> Bool {
        var iterator = makeIterator()

        guard var previous = iterator.next() else {
            // Sequence is empty
            return true
        }

        while let current = iterator.next() {
            guard try areInIncreasingOrder(previous, current) else {
                return false
            }

            previous = current
        }

        return true
    }
}

extension Sequence where Element : Comparable {
    func isSorted() -> Bool {
        return isSorted(by: <)
    }
}

1 Comment

Everything you did here to make an iterator, get its next elements, conditionally unwrap them and loop, is actually exactly what for does. This could just be for current in self { ... }
4

Other answers have incorporated allSatisfy, but not adjacentPairs, which makes this so easy that this extension may not be warranted.

import Algorithms

public extension Sequence where Element: Comparable {
  /// Whether the elements of the sequence are in order.
  @inlinable var isSorted: Bool { adjacentPairs().allSatisfy(<=) }
}
let random = Int.random(in: 1...(.max))
let stride = stride(from: -random, through: random, by: random)
XCTAssert(stride.isSorted)
XCTAssertFalse(stride.reversed().isSorted)

However, it's very common to want to use a property of the elements for this, not the elements themselves:

import Algorithms

public extension Sequence {
  /// Whether the elements of this sequence are sorted by a common `Comparable` value.
  @inlinable func isSorted<Comparable: Swift.Comparable>(
    by comparable: (Element) throws -> Comparable
  ) rethrows -> Bool {
    try isSorted(by: comparable, <=)
  }

  /// Whether the elements of this sequence are sorted by a common `Comparable` value,
  /// and sorting closure.
  @inlinable func isSorted<Comparable: Swift.Comparable>(
    by comparable: (Element) throws -> Comparable,
    _ areInIncreasingOrder: (Comparable, Comparable) throws -> Bool
  ) rethrows -> Bool {
    try adjacentPairs().allSatisfy {
      try areInIncreasingOrder(comparable($0), comparable($1))
    }
  }
}
struct TypeWithComparable {
  let comparable: Int
}

let random = Int.random(in: 1...(.max))
let stride =
  stride(from: -random, through: random, by: random)
    .lazy.map(TypeWithComparable.init)
XCTAssert(stride.isSorted(by: \.comparable))
XCTAssert(stride.reversed().isSorted(by: \.comparable, >=))

Comments

2

Adaptation, a solution that will work in Swift 4

extension Array where Iterator.Element: Comparable {
    func isSorted(isOrderedBefore: (Iterator.Element, Iterator.Element) -> Bool) -> Bool  {
        for i in 1 ..< self.count {
            if isOrderedBefore(self[i], self[i-1]) {
                return false
            }
        }
        return true
    }
}

Comments

1

The most flexible solution to me is a combination of NSAddict's and Wes Campaigne's answer. I.e. combine the advantage of being able to extend protocols and to pass comparator functions as arguments. This eliminates the restrictions both to use it only with arrays and to constrain it to elements conforming to Comparable protocol.

extension CollectionType
{
    func isSorted(isOrderedBefore: (Generator.Element, Generator.Element) -> Bool) -> Bool
    {
        var previousIndex = startIndex
        var currentIndex = startIndex.successor()

        while currentIndex != endIndex
        {
            if isOrderedBefore(self[previousIndex], self[currentIndex]) == false
            {
                return false
            }

            previousIndex = currentIndex
            currentIndex = currentIndex.successor()
        }

        return true
    }
}

This can be used on any Collection type and sorting criteria can be defined according to your needs.

1 Comment

And for Swift 3 replace someIndex.successor() with self.index(after: someIndex)
1

Here is a solution in Swift 4 that won't crash when self.count is equal or less than 1:

extension Array where Element: Comparable {
    func isSorted(by isOrderedBefore: (Element, Element) -> Bool) -> Bool {
        for i in stride(from: 1, to: self.count, by: 1) {
            if !isOrderedBefore(self[i-1], self[i]) {
                return false
            }
        }
        return true
    }
}

This snippet supposes that an array of 1 or 0 elements is already sorted.

The reason to start with 1 in the for-loop range is: In case self.count <= 1, the loop will be skipped, a small performance increase. Using stride instead of ..< avoids the crash when the upper bound is < than the lower bound of a range.

Here are some examples:

[1, 2, 3].isSorted(by: >) // true
[3, 2, 2].isSorted(by: >=) // true
[1, 4, 7].isSorted(by: {x, y in
    return x + 2 < y * y
}) // true

let a: [Int] = [1]
a.isSorted(by: <) // true


let b: [Int] = []
b.isSorted(by: >) // true

Comments

1

Summarising previous answers there is a smallest universal Array extension to check:

extension Array {
    func isSorted(_ predicate: (Element, Element) throws -> Bool) -> Bool {
        guard count > 1 else { return true }
        return (try? zip(self, self.dropFirst()).allSatisfy(predicate)) == true
    }
}

// Standard types

[1, 2, 3, 4, 5].isSorted(<) // true
[1, 2, 10, 4, 5].isSorted(<) // false

[10, 5, 4, 3, 1].isSorted(>) // true
[10, 20, 4, 3, 1].isSorted(>) // false


// Custom types

struct MyStruct {
    let i: Int
}

let items = [MyStruct(i: 1), MyStruct(i: 2), MyStruct(i: 3), MyStruct(i: 10)]
let sorted = items.isSorted { $0.i < $1.i } // true

Comments

0

If you want simple function without arguments, like sort() or sorted() in Swift:

extension Array where Element : Comparable {
    func isSorted() -> Bool {
        guard self.count > 1 else {
            return true
        }

        for i in 1..<self.count {
            if self[i-1] > self[i] {
                return false
            }
        }
        return true
    }
}

Comments

0

The generic function, zip(), can provide a shortcut for implementation.

extension Collection where Element: Comparable {
    var isSorted: Bool {
        guard count > 1 else {
            return true 
        }

        let pairs = zip(prefix(count - 1), suffix(count - 1))

        return !pairs.contains { previous, next in
            previous > next
        }
    }
}

[0, 1, 1, 2].isSorted  // true
[0, 2, 2, 1].isSorted  // false

Comments

0

@kAzec's answer seems to not working when elements are equal. This is because areInIncreasingOrder(a, a) must be false according to the documentation.

The following should work fine.

extension Sequence {
    func isSorted(by areInIncreasingOrder: (Element, Element) throws -> Bool)
        rethrows -> Bool {
        var it = makeIterator()
        guard var previous = it.next() else { return true }
        
        while let current = it.next() {
            if try !areInIncreasingOrder(previous, current) &&
                areInIncreasingOrder(current, previous) {
                return false
            }
            previous = current
        }
        return true
    }
}

extension Sequence where Element: Comparable {
    func isSorted() -> Bool {
        return isSorted(by: <)
    }
}

Comments

0

Just for fun. This supports duplicated elements that are equal as well:

extension Sequence {
    var neighbors: Zip2Sequence<Self, DropFirstSequence<Self>> {
        zip(self, dropFirst())
    }
    func isSorted<T: Comparable>(_ predicate: (Element) throws -> T) rethrows -> Bool {
        try isSorted(predicate, by: <)
    }
    func isSorted<T: Comparable>(
        _ predicate: (Element) throws -> T,
        by areInIncreasingOrder: (T, T) throws -> Bool
    ) rethrows -> Bool {
        try neighbors.allSatisfy {
            try areInIncreasingOrder(predicate($0), predicate($1)) ||
            predicate($0) == predicate($1)
        }
    }
}

extension Sequence where Element: Comparable {
    var isSorted: Bool { isSorted(by: <) }
    func isSorted(
        by areInIncreasingOrder: (Element, Element) throws -> Bool
    ) rethrows -> Bool {
        try neighbors.allSatisfy {
            try areInIncreasingOrder($0, $1) || $0 == $1
        }
    }
}

Usage:

[1,2,2,3].isSorted         // true
[3,2,2,1].isSorted(by: >)  // true

struct Test {
    let id: Int
}

[1,2,2,3].map(Test.init).isSorted(\.id)          // true
[3,2,2,1].map(Test.init).isSorted(\.id, by: >)   // true

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.