Array-Erweiterung zum Entfernen von Objekten nach Wert

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

Ich erhalte jedoch eine Fehlermeldung var index = find(self, object)

'T' kann nicht in 'T' konvertiert werden

Ich habe es auch mit dieser Methodensignatur versucht: func removeObject(object: AnyObject)Ich erhalte jedoch den gleichen Fehler:

'AnyObject' kann nicht in 'T' konvertiert werden.

Was ist der richtige Weg, um dies zu tun?

Schneemann
quelle
Versuchen Sie, das T whereaus Ihrer Methodendeklaration zu entfernen . Also einfach func removeObject<T: Equatable>. Diese Frage ist verwandt: stackoverflow.com/questions/24091046/…
ahruss

Antworten:

165

Ab Swift 2 kann dies mit einer Protokollerweiterungsmethode erreicht werden . removeObject()wird als Methode für alle Typen definiert, die RangeReplaceableCollectionType(insbesondere Array) entsprechen, wenn die Elemente der Sammlung 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)
        }
    }
}

Beispiel:

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

Update für Swift 2 / Xcode 7 Beta 2: Wie Airspeed Velocity in den Kommentaren feststellte, ist es jetzt tatsächlich möglich, eine Methode für einen generischen Typ zu schreiben, der die Vorlage restriktiver macht, sodass die Methode nun tatsächlich als Erweiterung definiert werden kann von Array:

extension Array where Element : Equatable {

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

Die Protokollerweiterung hat immer noch den Vorteil, dass sie auf einen größeren Satz von Typen anwendbar ist.

Update für 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.
quelle
1
Perfekt, du musst Swift lieben (2). Ich mag es wirklich, wie mit der Zeit mehr Dinge möglich werden und Dinge vereinfacht werden
Kametrixom
1
Ein guter Punkt, in vielerlei Hinsicht ist die Tatsache, dass die Antwort technisch immer noch korrekt ist, nur nicht mehr idiomatisch, noch schlimmer - die Leute werden kommen, die Antwort lesen und denken, eine freie Funktion sei der richtige Weg, um sie zu lösen, da es sich um eine hoch bewertete Antwort handelt . Ziemlich hässliches Szenario. Wird auf Meta posten.
Fluggeschwindigkeit
1
@ AirspeedVelocity: Wow, das habe ich verpasst. Wird es in den Versionshinweisen behandelt?
Martin R
1
Wenn Sie die gleiche Funktionalität wie ObjC wünschen (dh alle übereinstimmenden Objekte anstelle nur des ersten entfernen), können Sie "if" in "while"
ändern
2
Die Swift 3-Version ist großartig, aber ich würde ihre Deklaration leicht umbenennen remove(object: Element), um den Swift API-Designrichtlinien zu entsprechen und Ausführlichkeit zu vermeiden. Ich habe eine Bearbeitung eingereicht, die dies widerspiegelt.
Swiftcode
66

Sie können keine Methode für einen generischen Typ schreiben, der die Vorlage restriktiver macht.

HINWEIS : Ab Swift 2.0 können Sie jetzt Methoden schreiben, die die Vorlage einschränken. Wenn Sie Ihren Code auf 2.0 aktualisiert haben, finden Sie in anderen Antworten weiter unten neue Optionen, um dies mithilfe von Erweiterungen zu implementieren.

Der Grund, warum Sie den Fehler erhalten, 'T' is not convertible to 'T'ist, dass Sie tatsächlich ein neues T in Ihrer Methode definieren, das überhaupt nicht mit dem ursprünglichen T zusammenhängt. Wenn Sie T in Ihrer Methode verwenden möchten, können Sie dies tun, ohne es in Ihrer Methode anzugeben.

Der Grund, warum Sie den zweiten Fehler erhalten, 'AnyObject' is not convertible to 'T'ist, dass nicht alle möglichen Werte für T alle Klassen sind. Damit eine Instanz in AnyObject konvertiert werden kann, muss es sich um eine Klasse handeln (es kann sich nicht um eine Struktur, eine Aufzählung usw. handeln).

Am besten machen Sie es zu einer Funktion, die das Array als Argument akzeptiert:

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

Anstatt das ursprüngliche Array zu ändern, können Sie Ihre Methode threadsicherer und wiederverwendbarer machen, indem Sie eine Kopie zurückgeben:

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

Als Alternative, die ich nicht empfehle, kann Ihre Methode stillschweigend fehlschlagen, wenn der im Array gespeicherte Typ nicht in die Methodenvorlage konvertiert werden kann (das ist gleichwertig). (Aus Gründen der Übersichtlichkeit verwende ich U anstelle von T für die Vorlage der Methode):

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]

Bearbeiten Um den stillen Fehler zu überwinden, können Sie den Erfolg als Bool zurückgeben:

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
Drawag
quelle
Überprüfen Sie meine Antwort hier: stackoverflow.com/a/24939242/458960 Warum kann ich das so machen und die findMethode nicht verwenden ?
Schneemann
Ihre Methode ist anfällig für Laufzeitabstürze. Mit meiner Funktion verhindert der Compiler, dass dies überhaupt passiert.
Drawag
1
@Isuru Diese Methode funktioniert mit jedem Objekt, das das EquatableProtokoll implementiert . UIView tut dies ja, es wird mit UIViews funktionieren
drawag
4
Wow, schreibe eine for-Schleife, um ein Element zu entfernen, zurück in die 90er Jahre!
Zorayr
5
In letzter Zeit schnell. enumerate(self)muss reparieren zuself.enumerate()
TomSawyer
28

kurz und prägnant:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
János
quelle
2
Das ist cool. Natürlich kann es auch ohne das gemacht inoutwerden. Selbst mit dem inoutintakten könnte man verwenden, array = array.filter() { $0 != object }denke ich.
Dan Rosenstark
11
Achten Sie darauf, dass Sie den Force Unwrapped-Index verwenden, der Null sein kann. Ändern Sie zu "wenn let ind = index {array.removeAtIndex (ind)}"
HotJard
17

Nachdem ich all das gelesen habe, ist die beste Antwort für mich:

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

Stichprobe:

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

Swift 2 (xcode 7b4) Array-Erweiterung:

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

Stichprobe:

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

Swift 3.1 Update

Kam jetzt darauf zurück, da Swift 3.1 heraus ist. Im Folgenden finden Sie eine Erweiterung, die umfassende, schnelle, mutierende und erstellende Varianten bietet.

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
    }
}

Proben:

// 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"]
Dezember
quelle
Gibt dies nicht eine völlig neue Instanz des Arrays zurück?
pxpgraphics
Ja. Es ist ein funktionaler Stil. YMMV.
Dezember
Ich stimme dem funktionalen Styling eher zu, außer in diesem Fall, wenn die filterFunktion diese Funktionalität bereits für Sie übernimmt. Dies scheint die Funktionalität zu duplizieren. Aber trotzdem eine gute Antwort:]
pxpgraphics
13

Mit Protokollerweiterungen können Sie dies tun,

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

Gleiche Funktionalität für Klassen,

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)
        }
    }
}

Wenn eine Klasse Equatable implementiert, wird sie mehrdeutig und der Compiler gibt einen Fehler aus.

Sri Krishna Paritala
quelle
1
Ich erhalte einenBinary operator '===' cannot be applied to two elements of type '_' and 'Element'
Schuh
6

Mit Protokollerweiterungen in 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
quelle
4

Was ist mit Filterung? Das Folgende funktioniert auch mit [AnyObject] recht gut.

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

}
Valvolin
quelle
2

Es gibt eine andere Möglichkeit, ein Element aus einem Array zu entfernen, ohne dass eine unsichere Verwendung möglich ist, da der generische Typ des zu entfernenden Objekts nicht mit dem Typ des Arrays identisch sein kann. Die Verwendung von Optionen ist auch nicht der perfekte Weg, da sie sehr langsam sind. Sie können daher einen Verschluss verwenden, wie er beispielsweise bereits beim Sortieren eines Arrays verwendet wird.

//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
}

Wenn Sie die ArrayKlasse mit dieser Funktion erweitern, können Sie Elemente wie folgt entfernen:

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

Sie können ein Element jedoch nur dann entfernen, wenn es dieselbe Speicheradresse hat ( AnyObjectnatürlich nur für protokollkonforme Klassen ):

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'

Das Gute ist, dass Sie den zu vergleichenden Parameter angeben können. Wenn Sie beispielsweise ein Array von Arrays haben, können Sie den Gleichheitsabschluss als angeben, { $0.count == $1.count }und das erste Array mit der gleichen Größe wie das zu entfernende Array wird aus dem Array entfernt.

Sie können den Funktionsaufruf sogar verkürzen, indem Sie die Funktion als haben mutating func removeFirst(equality: (Element) -> Bool) -> Bool, dann die if-Auswertung durch ersetzen equality(item)und die Funktion beispielsweise durch aufrufen array.removeFirst({ $0 == "Banana" }).

Borchero
quelle
Da ==es sich um eine Funktion handelt, können Sie sie auch für jeden implementierten Typ ==(wie String, Int usw.) so aufrufen :array.removeFirst("Banana", equality:==)
Aviel Gross
@ AvielGross das ist neu in Swift 2 Ich denke
zögern
2

Keine Notwendigkeit zu verlängern:

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
quelle
1

Verwenden indexOfanstelle eines foroder 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
quelle
1

Vielleicht habe ich die Frage nicht verstanden.

Warum sollte das nicht funktionieren?

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
quelle
0

Am Ende hatte ich folgenden Code.

extension Array where Element: Equatable {

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

}
Kaz Yoshikawa
quelle
0

Ich habe es geschafft, a [String:AnyObject]aus einem Array zu entfernen, [[String:AnyObject]]indem ich eine Anzahl außerhalb einer for-Schleife implementiert habe, um den Index darzustellen, da .findund .filternicht kompatibel mit [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
quelle
-1

Implementierung in 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
quelle
-4

Ich konnte es zum Laufen bringen mit:

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!)
        }
    }
}
Schneemann
quelle
Der Vergleich if(index)ist ungültig
Juanjo