Essayer de comprendre comment Swift compare les tableaux.
var myArray1 : [String] = ["1","2","3","4","5"]
var myArray2 : [String] = ["1","2","3","4","5"]
// 1) Comparing 2 simple arrays
if(myArray1 == myArray2) {
println("Equality")
} else {
println("Equality no")
}
// -> prints equality -> thanks god
// 2) comparing to a "copy" of an array
// Swift copies arrays when passed as parameters (as per doc)
func arrayTest(anArray: [String]) -> Bool {
return anArray == myArray1
}
println("Array test 1 is \(arrayTest(myArray1))")
println("Array test 2 is \(arrayTest(myArray2))")
// equality works for both
myArray2.append("test")
println("Array test 2 is \(arrayTest(myArray2))")
// false (obviously)
myArray2.removeAtIndex(5)
println("Array test 2 is \(arrayTest(myArray2))")
// true
Apple dit qu'il y a des optimisations en coulisse sur les copies de tableaux. On dirait parfois - pas toujours - que les structures sont copiées ou non.
Cela dit,
1) == est-il itératif sur tout le tableau pour effectuer une comparaison basée sur les éléments? (ça y ressemble) -> Qu'en est-il des performances/de l'utilisation de la mémoire sur de très grands tableaux alors?
2) Sommes-nous sûrs que == retournera jamais vrai si tous les éléments sont égaux? J'ai de mauvais souvenirs de == sur Java Strings
3) Existe-t-il un moyen de vérifier si myArray1 et myArray2 utilisent techniquement le même "emplacement de mémoire"/pointeur/etc.? Je suis après avoir compris comment fonctionne l'optimisation et les mises en garde potentielles.
Merci.
Vous avez raison d’être légèrement nerveux à propos de ==
:
struct NeverEqual: Equatable { }
func ==(lhs: NeverEqual, rhs: NeverEqual)->Bool { return false }
let x = [NeverEqual()]
var y = x
x == y // this returns true
[NeverEqual()] == [NeverEqual()] // false
x == [NeverEqual()] // false
let z = [NeverEqual()]
x == z // false
x == y // true
y[0] = NeverEqual()
x == y // now false
Pourquoi? Les tableaux Swift ne sont pas conformes à Equatable
, mais ils ont un opérateur ==
, défini dans la bibliothèque standard comme suit:
func ==<T : Equatable>(lhs: [T], rhs: [T]) -> Bool
Cet opérateur effectue une boucle sur les éléments dans lhs
et rhs
, en comparant les valeurs à chaque position. Il fait pas fait une comparaison bit à bit - il appelle l'opérateur ==
sur chaque paire d'éléments. Cela signifie que si vous écrivez un ==
personnalisé pour votre élément, il sera appelé.
Mais il contient une optimisation - si les tampons sous-jacents des deux tableaux sont identiques, cela ne dérange pas, il retourne simplement true (ils contiennent des éléments identiques, bien sûr, ils sont égaux!).
Ce problème est entièrement la faute de l'opérateur NeverEqual
égalité. L'égalité doit être transitive, symétrique et réflexive, et celle-ci n'est pas réflexive (x == x
est faux). Mais il pourrait toujours vous prendre au dépourvu.
Les tableaux Swift sont des copies sur écriture. Ainsi, lorsque vous écrivez var x = y
, il ne crée pas de copie du tableau, il pointe simplement le pointeur du tampon de stockage de x
sur y
. Ce n'est que si x
ou y
sont mutés plus tard qu'il en fait une copie du tampon, de sorte que la variable non modifiée ne soit pas affectée. Cela est essentiel pour que les tableaux se comportent comme des types de valeur tout en restant performants.
Dans les premières versions de Swift, vous pouviez réellement appeler ===
sur les tableaux (également dans les premières versions, le comportement de mutation était un peu différent. Si vous mutiez x
, y
changerait également, même s'il avait été déclaré avec let
- ce qui a effrayé les gens. ils l'ont changé).
Vous pouvez un peu reproduire l'ancien comportement de ===
sur des tableaux avec cette astuce (très dépendante de l'implémentation, à ne pas utiliser, à l'exception des enquêtes pokers et poussantes):
let a = [1,2,3]
var b = a
a.withUnsafeBufferPointer { outer in
b.withUnsafeBufferPointer { inner in
println(inner.baseAddress == outer.baseAddress)
}
}
==
dans Swift est identique à equals()
de Java, il compare les valeurs.
===
dans Swift est identique au ==
de Java, il compare les références.
Dans Swift, vous pouvez comparer les valeurs de contenu de tableau aussi facilement que cela:
["1", "2"] == ["1", "2"]
Mais cela ne fonctionnera pas si vous voulez comparer des références:
var myArray1 = [NSString(string: "1")]
var myArray2 = [NSString(string: "1")]
myArray1[0] === myArray2[0] // false
myArray1[0] == myArray2[0] // true
Alors les réponses:
Cela dépend de comment voulez-vous comparer. Par exemple:
["1", "2"] == ["1", "2"] // true
Mais
["1", "2"] == ["2", "1"] // false
Si vous avez besoin que ce deuxième cas soit également vrai et que vous puissiez ignorer les valeurs répétitives, vous pouvez procéder comme suit:
Set(["1", "2"]) == Set(["2", "1"]) // true
(Utilisez NSSet pour Swift 2)
Les tableaux sont conformes à Equatable
dans Swift 4.1, annulant les mises en garde mentionnées dans les réponses précédentes. Ceci est disponible dans Xcode 9.3.
https://Swift.org/blog/conditional-conformance/
Mais le fait même qu'ils aient implémenté
==
ne signifie pasArray
ouOptional
conforme àEquatable
. Étant donné que ces types peuvent stocker des types non équivalents, nous devons pouvoir exprimer qu’ils ne sont équivalents que lorsqu’ils stockent un type équivalent.Cela signifiait que ces opérateurs
==
avaient une grosse limitation: ils ne pouvaient pas être utilisés à deux niveaux de profondeur.Avec la conformité conditionnelle, nous pouvons maintenant résoudre ce problème. Cela nous permet d'écrire que ces types sont conformes à
Equatable
- à l'aide de l'opérateur==
déjà défini - si les types sur lesquels ils sont basés sont équivalents.
Pour comparer des tableaux d'objets personnalisés, nous pouvons utiliser elementsEqual .
class Person {
let ID: Int!
let name: String!
init(ID: Int, name: String) {
self.ID = ID
self.name = name
}
}
let oldFolks = [Person(ID: 1, name: "Ann"), Person(ID: 2, name: "Tony")]
let newFolks = [Person(ID: 2, name: "Tony"), Person(ID: 4, name: "Alice")]
if oldFolks.elementsEqual(newFolks, by: { $0.ID == $1.ID }) {
print("Same people in same order")
} else {
print("Nope")
}
Si vous avez un tableau de objets personnalisés , vous devez faire attention au test d'égalité, au moins avec Swift 4.1:
Si l'objet personnalisé est pas une sous-classe de NSObject
, la comparaison utilise la fonction static func == (lhs: Nsobject, rhs: Nsobject) -> Bool
, qui doit être définie.
Si est une sous-classe de NSObject
, il utilise la fonction func isEqual(_ object: Any?) -> Bool
, qui doit être remplacée.
Veuillez vérifier le code suivant et définir des points d'arrêt pour toutes les instructions de retour.
class Object: Equatable {
static func == (lhs: Object, rhs: Object) -> Bool {
return true
}
}
La classe suivante hérite de Equatable
de NSObject
class Nsobject: NSObject {
static func == (lhs: Nsobject, rhs: Nsobject) -> Bool {
return true
}
override func isEqual(_ object: Any?) -> Bool {
return true
}
}
Ils peuvent être comparés avec:
let nsObject1 = Nsobject()
let nsObject2 = Nsobject()
let nsObjectArray1 = [nsObject1]
let nsObjectArray2 = [nsObject2]
let _ = nsObjectArray1 == nsObjectArray2
let object1 = Object()
let object2 = Object()
let objectArray1 = [object1]
let objectArray2 = [object2]
let _ = objectArray1 == objectArray2