web-dev-qa-db-fra.com

Extension de tableau pour supprimer objet par valeur

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

Cependant, je reçois une erreur sur var index = find(self, object)

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

J'ai aussi essayé avec cette méthode signature: 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 faire cela?

134
Snowman

A partir de Swift 2 , ceci peut être réalisé avec une méthode d'extension de protocole .removeObject() est définie comme une méthode pour tous les types conformesto RangeReplaceableCollectionType (notamment 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 maintenant possible d'écrire une méthode sur un type générique plus restrictif pour le modèle, ainsi la méthode pourrait maintenant être défini 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)
        }
    }
}
164
Martin R

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

NOTE: à partir de Swift 2.0, vous pouvez maintenant écrire des méthodes qui sont plus restrictives pour le modèle. Si vous avez mis à niveau votre code vers la version 2.0, reportez-vous aux autres réponses plus bas pour connaître les nouvelles options permettant de l'implémenter à l'aide d'extensions.

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

La deuxième erreur 'AnyObject' is not convertible to 'T' est due au fait 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 (il ne peut s'agir d'une structure, d'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 thread-safe 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 également faire échouer votre méthode si le type stocké dans le tableau ne peut pas être converti dans le modèle de méthodes (ce qui est équitable). (Par souci 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 en tant que bool:

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
65
drewag

brièvement et de façon concise:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
28
János

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

Nous sommes revenus à cela maintenant que Swift 3.1 est sorti. Vous trouverez ci-dessous une extension qui fournit des variantes exhaustives, rapides, en mutation et en création. 

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"]
17
rosstulloch

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 classes, 

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 génère une erreur.

13

Avec utiliser des 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)
        }
    }
}
7
ogantopkaya

qu'en est-il d'utiliser le 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})
    }

}
4
valvoline

Il existe une autre possibilité de supprimer un élément d'un tableau sans utilisation potentiellement dangereuse, car le type générique de l'objet à supprimer ne peut pas être identique au 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 si elle était 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 classe Array 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 possède la même adresse mémoire (uniquement pour les classes conformes au protocole AnyObject, 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é sous la forme { $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 lui attribuant la valeur 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.

2
borchero

Utiliser indexOf au lieu d'une for ou 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
          }
       }
   }   
}
1
juanjo

J'ai réussi à supprimer un [String:AnyObject] d'un tableau [[String:AnyObject]] en implémentant un nombre extérieur à une boucle for pour représenter l'index, car .find et .filter n'étaient pas compatibles 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
}
0
Tobias Brysiewicz

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

Pourquoi ça ne marcherait 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
0
Little Green Viper

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
    }

}
0
Kaz Yoshikawa