Расширение массива, чтобы проверить, отсортировано ли оно в Swift?
Я хочу расширить класс Array, чтобы он мог знать, будет ли он отсортирован (по возрастанию) или нет. Я хочу добавить вычисляемое свойство isSorted
. Как можно сопоставить элементы массива?
Моя текущая реализация в 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
Ошибка Could not find an overload for '>' that accepts the supplied arguments
Конечно, у меня все еще есть ошибка, потому что Swift не знает, как сравнивать элементы. Как реализовать это расширение в Swift? Или я здесь что-то не так?
Ответы
Ответ 1
Альтернативным решением для бесплатной функции является выполнение того, что делают встроенные методы Array.sort
и Array.sorted
Swift, и требуют, чтобы вы передали подходящий компаратор методу:
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
Ответ 2
В Swift 2.0 теперь вы можете расширять протоколы!
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
Обратите внимание, что, поскольку мы используем Indexable.Index
вместо простого Int
в качестве индекса, мы должны использовать while-loop вместо этого, который выглядит немного менее красивым и понятным.
Ответ 3
Вы столкнулись с проблемой с генериками Swift, которые не могут быть решены так, как вам нравится прямо сейчас (возможно, в будущей версии Swift). См. Также Проблема Swift Generics.
В настоящее время вам необходимо определить функцию (например, в глобальной области):
func isSorted<T: Comparable>(array: Array<T>) -> Bool {
for i in 1..<array.count {
if array[i-1] > array[i] {
return false
}
}
return true
}
let i = [1, 2, 3]
let j = [2, 1, 3]
let k = [UIView(), UIView()]
println(isSorted(i)) // Prints "true"
println(isSorted(j)) // Prints "false"
println(isSorted(k)) // Error: Missing argument for parameter #2 in call
Сообщение об ошибке вводит в заблуждение, ИМХО, поскольку фактическая ошибка - это что-то вроде "UIView не удовлетворяет типу ограничений Comparable".
Ответ 4
На самом деле, вы можете расширить протокол Sequence
для более общего решения:
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: <)
}
}
Ответ 5
Адаптация, решение, которое будет работать в 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
}
}
Ответ 6
Наиболее гибким решением для меня является сочетание ответа NSAddict и Wes Campaigne. То есть объединить преимущество возможности расширять протоколы и передавать функции компаратора в качестве аргументов. Это устраняет ограничения как для использования только с массивами, так и для ограничения его на элементы, соответствующие протоколу Comparable
.
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
}
}
Это может использоваться для любого типа Collection
, и критерии сортировки могут быть определены в соответствии с вашими потребностями.
Ответ 7
Общая функция, zip()
, может предоставить ярлык для реализации.
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
Ответ 8
Вот решение в Swift 4, которое не будет аварийно self.count
если self.count
равно или меньше 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
}
}
Этот фрагмент предполагает, что массив из 1 или 0 элементов уже отсортирован.
Причина для начала с 1 в диапазоне цикла for: в случае self.count <= 1 цикл будет пропущен, что приведет к небольшому увеличению производительности. Использование stride
вместо ..<
позволяет избежать сбоя, когда верхняя граница <ниже нижней границы диапазона.
Вот некоторые примеры:
[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
Ответ 9
Если вам нужна простая функция без аргументов, такая как sort() или sorted() в 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
}
}
Ответ 10
В Swift 4.2 и более поздних версиях вы можете объединить allSatisfy
и zip
с помощью некоторого среза последовательности:
extension Array where Element: Comparable{
func isAscending() -> Bool {
return zip(self, self.dropFirst()).allSatisfy(<=)
}
func isDescending() -> Bool {
return zip(self, self.dropFirst()).allSatisfy(>=)
}
}