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?
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
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.
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".
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: <)
}
}
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
}
}
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.
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
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
}
}
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