web-dev-qa-db-fra.com

Comparez les tableaux dans Swift

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.

48
vivien.destpern

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) 
    } 
}
66
Airspeed Velocity

== 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:

  1. Je pense que la performance est optimale pour faire de la valeur (pas de référence) Comparaisons
  2. Oui, si vous voulez comparer des valeurs
  3. Les tableaux Swift sont des types valeur et non type référence. Ainsi, l'emplacement mémoire N'est identique que si vous le comparez à lui-même (ou utilisez des pointeurs non sécuritaires .__
17
Kirsteins

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)

5
demosten

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 pas Array ou Optional 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.

4
Greg McCoy

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")
}
3
tier777

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
1
Reinhard Männer