Extension de tableau pour supprimer un objet par valeur

140
extension Array {
    func removeObject<T where T : Equatable>(object: T) {
        var index = find(self, object)
        self.removeAtIndex(index)
    }
}

Cependant, j'obtiens une erreur sur var index = find(self, object)

«T» n'est pas convertible en «T»

J'ai aussi essayé avec cette signature de méthode: func removeObject(object: AnyObject)cependant, j'obtiens la même erreur:

'AnyObject' n'est pas convertible en 'T'

Quelle est la bonne façon de procéder?

Bonhomme de neige
la source
Essayez de supprimer le T wherede votre déclaration de méthode. Tellement juste func removeObject<T: Equatable>. Cette question est liée: stackoverflow.com/questions/24091046/…
ahruss

Réponses:

165

À partir de Swift 2 , cela peut être réalisé avec une méthode d'extension de protocole . removeObject()se définit comme une méthode sur tous les types conforme à RangeReplaceableCollectionType(en particulier sur Array) si les éléments de la collection sont Equatable:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func removeObject(object : Generator.Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }
}

Exemple:

var ar = [1, 2, 3, 2]
ar.removeObject(2)
print(ar) // [1, 3, 2]

Mise à jour pour Swift 2 / Xcode 7 beta 2: Comme Airspeed Velocity l'a remarqué dans les commentaires, il est désormais possible d'écrire une méthode sur un type générique plus restrictif sur le modèle, donc la méthode pourrait désormais être définie comme une extension de Array:

extension Array where Element : Equatable {

    // ... same method as above ...
}

L'extension de protocole a toujours l'avantage d'être applicable à un plus grand ensemble de types.

Mise à jour pour Swift 3:

extension Array where Element: Equatable {

    // Remove first collection element that is equal to the given `object`:
    mutating func remove(object: Element) {
        if let index = index(of: object) {
            remove(at: index)
        }
    }
}
Martin R
la source
1
Parfait, tu dois aimer Swift (2). J'aime vraiment comment avec le temps, plus de choses deviennent possibles et les choses se simplifient
Kametrixom
1
Bon point, à bien des égards, le fait que la réponse soit toujours techniquement correcte, tout simplement plus idiomatique, est encore pire - les gens viendront, liront la réponse, pensent qu'une fonction gratuite est la bonne façon de le résoudre car c'est une réponse très appréciée . Scénario assez laid. Publiera sur meta.
Airspeed Velocity
1
@AirspeedVelocity: Wow, j'ai raté ça. Est-ce couvert dans les notes de publication?
Martin R
1
Si vous voulez la même fonctionnalité que ObjC (c'est-à-dire supprime tous les objets correspondants au lieu du seul premier), vous pouvez changer "if" en "while"
powertoold
2
La version Swift 3 est excellente, mais je renommerais légèrement sa déclaration remove(object: Element)en afin de se conformer aux directives de conception de l'API Swift et d'éviter la verbosité. J'ai soumis une modification reflétant cela.
swiftcode
66

Vous ne pouvez pas écrire une méthode sur un type générique qui est plus restrictif sur le modèle.

REMARQUE : à partir de Swift 2.0, vous pouvez maintenant écrire des méthodes qui sont plus restrictives sur le modèle. Si vous avez mis à niveau votre code vers 2.0, consultez les autres réponses ci-dessous pour de nouvelles options pour implémenter cela à l'aide d'extensions.

La raison pour laquelle vous obtenez l'erreur 'T' is not convertible to 'T' est que vous définissez en fait un nouveau T dans votre méthode qui n'est pas du tout lié au T. d'origine. Si vous souhaitez utiliser T dans votre méthode, vous pouvez le faire sans le spécifier dans votre méthode.

La raison pour laquelle vous obtenez la deuxième erreur 'AnyObject' is not convertible to 'T' est que toutes les valeurs possibles pour T ne sont pas toutes des classes. Pour qu'une instance soit convertie en AnyObject, il doit s'agir d'une classe (ce ne peut pas être une structure, une énumération, etc.).

Votre meilleur pari est d'en faire une fonction qui accepte le tableau comme argument:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) {
}

Ou au lieu de modifier le tableau d'origine, vous pouvez rendre votre méthode plus sûre pour les threads et réutilisable en renvoyant une copie:

func arrayRemovingObject<T : Equatable>(object: T, fromArray array: [T]) -> [T] {
}

Comme alternative que je ne recommande pas, vous pouvez faire échouer votre méthode en silence si le type stocké dans le tableau ne peut pas être converti en modèle de méthodes (qui peut être égalisé). (Pour plus de clarté, j'utilise U au lieu de T pour le modèle de la méthode):

extension Array {
    mutating func removeObject<U: Equatable>(object: U) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            if let to = objectToCompare as? U {
                if object == to {
                    index = idx
                }
            }
        }

        if(index != nil) {
            self.removeAtIndex(index!)
        }
    }
}

var list = [1,2,3]
list.removeObject(2) // Successfully removes 2 because types matched
list.removeObject("3") // fails silently to remove anything because the types don't match
list // [1, 3]

Edit Pour surmonter l'échec silencieux, vous pouvez renvoyer le succès sous forme de valeur booléenne:

extension Array {
  mutating func removeObject<U: Equatable>(object: U) -> Bool {
    for (idx, objectToCompare) in self.enumerate() {  //in old swift use enumerate(self) 
      if let to = objectToCompare as? U {
        if object == to {
          self.removeAtIndex(idx)
          return true
        }
      }
    }
    return false
  }
}
var list = [1,2,3,2]
list.removeObject(2)
list
list.removeObject(2)
list
dessiner
la source
Consultez ma réponse ici: stackoverflow.com/a/24939242/458960 Pourquoi suis-je capable de le faire de cette façon et de ne pas utiliser la findméthode?
Snowman
Votre méthode est susceptible de planter lors de l'exécution. Avec ma fonction, le compilateur empêchera cela du tout.
tirage du
1
@Isuru Cette méthode fonctionne avec n'importe quel objet qui implémente le Equatableprotocole. UIView le fait oui, il fonctionnera avec UIViews
dessiné
4
Wow, écrire une boucle for pour supprimer un élément, retour aux années 90!
Zorayr
5
Dans le dernier swift. enumerate(self)doit réparer àself.enumerate()
TomSawyer
29

brièvement et de manière concise:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
János
la source
2
C'est cool. Bien sûr, cela peut aussi être fait sans inout. Même avec le inoutintact, on pourrait utiliser array = array.filter() { $0 != object }, je pense.
Dan Rosenstark
11
Soyez conscient de l'utilisation d'index forcé, qui peut être nul. Remplacer par "if let ind = index {array.removeAtIndex (ind)}"
HotJard
17

Après avoir lu tout ce qui précède, à mon avis, la meilleure réponse est:

func arrayRemovingObject<U: Equatable>(object: U, # fromArray:[U]) -> [U] {
  return fromArray.filter { return $0 != object }
}

Échantillon:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = arrayRemovingObject("Cat", fromArray:myArray )

Extension de tableau Swift 2 (xcode 7b4):

extension Array where Element: Equatable {  
  func arrayRemovingObject(object: Element) -> [Element] {  
    return filter { $0 != object }  
  }  
}  

Échantillon:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = myArray.arrayRemovingObject("Cat" )

Mise à jour Swift 3.1

J'y suis revenu maintenant que Swift 3.1 est sorti. Vous trouverez ci-dessous une extension qui fournit des variantes exhaustives, rapides, mutantes et créatrices.

extension Array where Element:Equatable {
    public mutating func remove(_ item:Element ) {
        var index = 0
        while index < self.count {
            if self[index] == item {
                self.remove(at: index)
            } else {
                index += 1
            }
        }
    }

    public func array( removing item:Element ) -> [Element] {
        var result = self
        result.remove( item )
        return result
    }
}

Échantillons:

// Mutation...
      var array1 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      array1.remove("Cat")
      print(array1) //  ["Dog", "Turtle", "Socks"]

// Creation...
      let array2 = ["Cat", "Dog", "Turtle", "Cat", "Fish", "Cat"]
      let array3 = array2.array(removing:"Cat")
      print(array3) // ["Dog", "Turtle", "Fish"]
décembre
la source
cela ne renvoie-t-il pas une instance complètement nouvelle du tableau?
pxpgraphics
Oui. C'est un style plus fonctionnel. YMMV.
Décembre
J'ai tendance à être d'accord avec le style fonctionnel, sauf, dans ce cas, lorsque la filterfonction gère déjà cette fonctionnalité pour vous. Cela semble dupliquer la fonctionnalité. Mais une bonne réponse tout de même:]
pxpgraphics
13

Avec les extensions de protocole, vous pouvez le faire,

extension Array where Element: Equatable {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 == object }) {
            removeAtIndex(index)
        }
    }
}

Même fonctionnalité pour les cours,

Swift 2

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = indexOf({ $0 === object }) {
            removeAtIndex(index)
        }
    }
}

Swift 3

extension Array where Element: AnyObject {
    mutating func remove(object: Element) {
        if let index = index(where: { $0 === object }) {
             remove(at: index)
        }
    }
}

Mais si une classe implémente Equatable, cela devient ambigu et le compilateur renvoie une erreur.

Sri Krishna Paritala
la source
1
Je reçois uneBinary operator '===' cannot be applied to two elements of type '_' and 'Element'
chaussure
6

Avec l'utilisation d'extensions de protocole dans Swift 2.0

extension _ArrayType where Generator.Element : Equatable{
    mutating func removeObject(object : Self.Generator.Element) {
        while let index = self.indexOf(object){
            self.removeAtIndex(index)
        }
    }
}
ogantopkaya
la source
4

qu'en est-il du filtrage? ce qui suit fonctionne assez bien même avec [AnyObject].

import Foundation
extension Array {
    mutating func removeObject<T where T : Equatable>(obj: T) {
        self = self.filter({$0 as? T != obj})
    }

}
valvoline
la source
2

Il existe une autre possibilité de supprimer un élément d'un tableau sans avoir une utilisation non sécurisée possible, car le type générique de l'objet à supprimer ne peut pas être le même que le type du tableau. L'utilisation d'options n'est pas non plus la solution idéale car elles sont très lentes. Vous pouvez donc utiliser une fermeture comme elle est déjà utilisée lors du tri d'un tableau par exemple.

//removes the first item that is equal to the specified element
mutating func removeFirst(element: Element, equality: (Element, Element) -> Bool) -> Bool {
    for (index, item) in enumerate(self) {
        if equality(item, element) {
            self.removeAtIndex(index)
            return true
        }
    }
    return false
}

Lorsque vous étendez la Arrayclasse avec cette fonction, vous pouvez supprimer des éléments en procédant comme suit:

var array = ["Apple", "Banana", "Strawberry"]
array.removeFirst("Banana") { $0 == $1 } //Banana is now removed

Cependant, vous pouvez même supprimer un élément uniquement s'il a la même adresse mémoire (uniquement pour les classes conformes au AnyObjectprotocole, bien sûr):

let date1 = NSDate()
let date2 = NSDate()
var array = [date1, date2]
array.removeFirst(NSDate()) { $0 === $1 } //won't do anything
array.removeFirst(date1) { $0 === $1 } //array now contains only 'date2'

La bonne chose est que vous pouvez spécifier le paramètre à comparer. Par exemple, lorsque vous avez un tableau de tableaux, vous pouvez spécifier la fermeture d'égalité comme{ $0.count == $1.count } et le premier tableau ayant la même taille que celui à supprimer est supprimé du tableau.

Vous pouvez même raccourcir l'appel de fonction en ayant la fonction as mutating func removeFirst(equality: (Element) -> Bool) -> Bool, puis remplacer if-evaluation par equality(item)et appeler la fonction par array.removeFirst({ $0 == "Banana" })par exemple.

borchero
la source
Puisqu'il ==s'agit d'une fonction, vous pouvez également l'appeler comme ceci pour tout type qui implémente ==(comme String, Int, etc.):array.removeFirst("Banana", equality:==)
Aviel Gross
@AvielGross c'est nouveau dans Swift 2 je pense - n'hésitez pas à modifier la réponse en conséquence si vous le souhaitez
borchero
2

Pas besoin de prolonger:

var ra = [7, 2, 5, 5, 4, 5, 3, 4, 2]

print(ra)                           // [7, 2, 5, 5, 4, 5, 3, 4, 2]

ra.removeAll(where: { $0 == 5 })

print(ra)                           // [7, 2, 4, 3, 4, 2]

if let i = ra.firstIndex(of: 4) {
    ra.remove(at: i)
}

print(ra)                           // [7, 2, 3, 4, 2]

if let j = ra.lastIndex(of: 2) {
    ra.remove(at: j)
}

print(ra)                           // [7, 2, 3, 4]
Roi Zakai
la source
1

En utilisant indexOfau lieu d'un forou enumerate:

extension Array where Element: Equatable {

   mutating func removeElement(element: Element) -> Element? {
      if let index = indexOf(element) {
         return removeAtIndex(index)
      }
      return nil
   }

   mutating func removeAllOccurrencesOfElement(element: Element) -> Int {
       var occurrences = 0
       while true {
          if let index = indexOf(element) {
             removeAtIndex(index)
             occurrences++
          } else {
             return occurrences
          }
       }
   }   
}
Juanjo
la source
1

Peut-être que je n'ai pas compris la question.

Pourquoi cela ne fonctionnerait-il pas?

import Foundation
extension Array where Element: Equatable {
    mutating func removeObject(object: Element) {
        if let index = self.firstIndex(of: object) {
            self.remove(at: index)
        }
    }
}

var testArray = [1,2,3,4,5,6,7,8,9,0]
testArray.removeObject(object: 6)
let newArray = testArray

var testArray2 = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "0"]
testArray2.removeObject(object: "6")
let newArray2 = testArray2
Chris Marshall
la source
0

J'ai finalement fini avec le code suivant.

extension Array where Element: Equatable {

    mutating func remove<Element: Equatable>(item: Element) -> Array {
        self = self.filter { $0 as? Element != item }
        return self
    }

}
Kaz Yoshikawa
la source
0

J'ai réussi à supprimer un [String:AnyObject]d'un tableau [[String:AnyObject]]en implémentant un compte en dehors d'une boucle for pour représenter l'index depuis .findet je ne suis .filterpas compatible avec [String:AnyObject].

let additionValue = productHarvestChoices[trueIndex]["name"] as! String
var count = 0
for productHarvestChoice in productHarvestChoices {
  if productHarvestChoice["name"] as! String == additionValue {
    productHarvestChoices.removeAtIndex(count)
  }
  count = count + 1
}
Tobias Brysiewicz
la source
-1

Implémentation dans Swift 2:

extension Array {
  mutating func removeObject<T: Equatable>(object: T) -> Bool {
    var index: Int?
    for (idx, objectToCompare) in self.enumerate() {
      if let toCompare = objectToCompare as? T {
        if toCompare == object {
          index = idx
          break
        }
      }
    }
    if(index != nil) {
      self.removeAtIndex(index!)
      return true
    } else {
      return false
    }
  }
}
wcharysz
la source
-4

J'ai pu le faire fonctionner avec:

extension Array {
    mutating func removeObject<T: Equatable>(object: T) {
        var index: Int?
        for (idx, objectToCompare) in enumerate(self) {
            let to = objectToCompare as T
            if object == to {
                index = idx
            }
        }

        if(index) {
            self.removeAtIndex(index!)
        }
    }
}
Bonhomme de neige
la source
La comparaison if(index)est invalide
juanjo