web-dev-qa-db-fra.com

Extension du tableau pour vérifier s'il est trié dans Swift?

Je souhaite étendre la classe Array afin qu’elle sache si elle est triée (par ordre croissant) ou non. Je veux ajouter une propriété calculée appelée isSorted. Comment puis-je affirmer que les éléments du tableau sont comparables?

Ma mise en œuvre actuelle dans 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

L'erreur Could not find an overload for '>' that accepts the supplied arguments

Bien sûr, il me reste une erreur car Swift ne sait pas comparer les éléments. Comment puis-je implémenter cette extension dans Swift? Ou est-ce que je fais quelque chose de mal ici?

20
Ikhsan Assaat

La solution alternative à une fonction libre consiste à faire ce que font les méthodes Array.sort et Array.sorted intégrées de Swift et à exiger que vous passiez un comparateur approprié à la méthode:

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
27
Wes Campaigne

Dans Swift 2.0, vous pouvez maintenant étendre les protocoles!

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

Notez que, parce que nous utilisons Indexable.Index au lieu d'une simple Int comme index, nous devons utiliser une boucle while, ce qui semble un peu moins joli et clair.

21
NSAddict

Vous avez rencontré un problème avec les génériques de Swift qui ne peut pas être résolu comme vous l'aimez pour le moment (peut-être dans une future version de Swift). Voir aussi numéro de Swift Generics .

Actuellement, vous devez définir une fonction (par exemple au niveau global):

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

Le message d'erreur est trompeur, à mon humble avis, car l'erreur réelle est du type "UIView ne satisfait pas la contrainte de type Comparable".

7
DarkDust

En fait, vous pouvez étendre le protocole Sequence à une solution plus générique:

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: <)
    }
}
3
kAzec

Adaptation, une solution qui fonctionnera dans 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
    }
}
2
adnv

La solution la plus flexible pour moi est une combinaison des réponses de NSAddict et de Wes Campaigne. C'est à dire. combine l'avantage de pouvoir étendre des protocoles et de passer des fonctions de comparaison en arguments. Cela élimine les restrictions imposées à son utilisation uniquement avec des tableaux et à sa contrainte aux éléments conformes au protocole 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
    }
}

Cela peut être utilisé sur tout type Collection et les critères de tri peuvent être définis en fonction de vos besoins.

1
hacker2007

La fonction générique, Zip() , peut fournir un raccourci pour la mise en œuvre.

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
0
otto

Si vous voulez une fonction simple sans arguments, comme sort () ou sortit () dans 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
    }
}
0
Ivo Leko

Voici une solution dans Swift 4 qui ne plantera pas lorsque self.count est égal ou inférieur à 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
    }
}

Cet extrait suppose qu'un tableau de 1 ou 0 élément est déjà trié.

La raison de commencer par 1 dans la plage de la boucle for est la suivante: Dans le cas où self.count <= 1, la boucle sera ignorée, ce qui représente une légère augmentation des performances. Utiliser stride au lieu de ..< évite le crash lorsque la limite supérieure est inférieure à la limite inférieure d'une plage.

Voici quelques exemples:

[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
0
Carpsen90