Rozszerzenie tablicy do usuwania obiektu według wartości

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

Jednak pojawia się błąd var index = find(self, object)

„T” nie można zamienić na „T”

Próbowałem też z tym podpisem metody: func removeObject(object: AnyObject)jednak pojawia się ten sam błąd:

„AnyObject” nie jest zamienialne na „T”

Jaki jest właściwy sposób, aby to zrobić?

Bałwan
źródło
Spróbuj usunąć T wherez deklaracji metody. Więc po prostu func removeObject<T: Equatable>. To pytanie jest powiązane: stackoverflow.com/questions/24091046/…
ahruss

Odpowiedzi:

165

Od wersji Swift 2 można to osiągnąć za pomocą metody rozszerzania protokołu . removeObject()jest definiowana jako metoda na wszystkich typach zgodna RangeReplaceableCollectionType(w szczególności z Array), jeśli elementy kolekcji to 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)
        }
    }
}

Przykład:

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

Aktualizacja dla Swift 2 / Xcode 7 beta 2: Jak zauważyliśmy w komentarzach Airspeed Velocity, obecnie w rzeczywistości możliwe jest napisanie metody na typie ogólnym, który jest bardziej restrykcyjny w szablonie, więc metodę można teraz zdefiniować jako rozszerzenie z Array:

extension Array where Element : Equatable {

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

Rozszerzenie protokołu nadal ma tę zaletę, że można je zastosować do większego zestawu typów.

Aktualizacja dla 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.
źródło
1
Idealnie, musisz pokochać Swifta (2). Naprawdę podoba mi się to, jak z czasem staje się więcej rzeczy i rzeczy się upraszczają
Kametrixom
1
Słuszna uwaga, pod wieloma względami fakt, że odpowiedź jest nadal poprawna technicznie, po prostu nie jest już idiomatyczna, jest jeszcze gorszy - ludzie przyjdą, przeczytają odpowiedź, pomyślą, że bezpłatna funkcja jest właściwym sposobem jej rozwiązania, ponieważ jest to wysoko oceniana odpowiedź . Całkiem brzydki scenariusz. Opublikuje na meta.
Prędkość lotu,
1
@AirspeedVelocity: Wow, przegapiłem to. Czy jest to uwzględnione w informacjach o wydaniu?
Martin R
1
Jeśli chcesz mieć tę samą funkcjonalność co ObjC (tj. Usuwa wszystkie pasujące obiekty zamiast tylko pierwszego), możesz zmienić „if” na „while”
powertoold
2
Wersja Swift 3 jest świetna, ale zmieniłbym nieco jej nazwę na remove(object: Element), aby zachować zgodność z wytycznymi projektowymi Swift API i uniknąć gadatliwości. Przesłałem zmianę, która to odzwierciedla.
swiftcode
66

Nie można napisać metody w typie ogólnym, który jest bardziej restrykcyjny w szablonie.

UWAGA : od wersji Swift 2.0 możesz teraz pisać metody, które bardziej restrykcyjne w szablonie. Jeśli zaktualizowałeś swój kod do wersji 2.0, zapoznaj się z innymi odpowiedziami poniżej, aby poznać nowe opcje implementacji tego za pomocą rozszerzeń.

Powodem, dla którego otrzymujesz błąd, 'T' is not convertible to 'T'jest to, że w rzeczywistości definiujesz nowe T w swojej metodzie, które nie jest w ogóle związane z oryginalnym T. Jeśli chcesz użyć T w swojej metodzie, możesz to zrobić bez określania go w swojej metodzie.

Powodem drugiego błędu 'AnyObject' is not convertible to 'T'jest to, że nie wszystkie możliwe wartości T są klasami. Aby instancja została przekonwertowana na AnyObject, musi być klasą (nie może to być struktura, wyliczenie itp.).

Najlepszym rozwiązaniem jest uczynienie z niej funkcji akceptującej tablicę jako argument:

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

Zamiast modyfikować oryginalną tablicę, możesz uczynić swoją metodę bardziej bezpieczną dla wątków i wielokrotnego użytku, zwracając kopię:

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

Alternatywą, której nie polecam, może być cicha awaria metody, jeśli typ przechowywany w tablicy nie może zostać przekonwertowany na szablon metod (jest to równoważne). (Dla jasności używam U zamiast T jako szablonu metody):

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]

Edytuj Aby przezwyciężyć cichą porażkę, możesz zwrócić sukces jako wartość logiczną:

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
drewag
źródło
Sprawdź moją odpowiedź tutaj: stackoverflow.com/a/24939242/458960 Dlaczego mogę to zrobić w ten sposób i nie używam tej findmetody?
Snowman
Twoja metoda jest podatna na awarie w czasie wykonywania. Dzięki mojej funkcji kompilator w ogóle temu zapobiegnie.
drewag
1
@Isuru Ta metoda działa z każdym obiektem, który implementuje Equatableprotokół. UIView robi tak, tak, będzie działać z UIViews
drewag
4
Wow, pisząc pętlę for, aby usunąć element, to już jest w latach 90-tych!
Zorayr
5
W najnowszym swift. enumerate(self)trzeba naprawićself.enumerate()
TomSawyer
29

krótko i zwięźle:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
János
źródło
2
To jest fajne. Oczywiście można to zrobić również bez inout. Myślę, że nawet z inoutnienaruszonym array = array.filter() { $0 != object }przydałoby się.
Dan Rosenstark
11
Należy pamiętać o używaniu indeksu wymuszonego rozpakowania, który może być zerowy. Zmień na „if let ind = index {array.removeAtIndex (ind)}”
HotJard
17

Po przeczytaniu powyższego uważam, że najlepszą odpowiedzią jest:

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

Próba:

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

Rozszerzenie tablicy Swift 2 (xcode 7b4):

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

Próba:

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

Aktualizacja Swift 3.1

Wróciłem do tego teraz, gdy Swift 3.1 wyszedł. Poniżej znajduje się rozszerzenie, które zapewnia wyczerpujące, szybkie, mutujące i tworzenie wariantów.

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

Próbki:

// 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"]
grudzień
źródło
czy to nie zwraca zupełnie nowej instancji tablicy?
pxpgraphics
Tak. To bardziej funkcjonalny styl. YMMV.
grudnia
Zwykle zgadzam się ze stylizacją funkcjonalną, z wyjątkiem tego przypadku, gdy filterfunkcja już obsługuje tę funkcjonalność za Ciebie. Wydaje się, że powiela to funkcjonalność. Ale mimo wszystko dobra odpowiedź:]
pxpgraphics
13

Dzięki rozszerzeniom protokołu możesz to zrobić,

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

Ta sama funkcjonalność dla zajęć,

Szybki 2

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

Szybki 3

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

Ale jeśli klasa implementuje Equatable, staje się niejednoznaczna, a kompilator zgłasza błąd.

Sri Krishna Paritala
źródło
1
dostaję Binary operator '===' cannot be applied to two elements of type '_' and 'Element'
but
6

Korzystając z rozszerzeń protokołów w 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
źródło
4

co powiesz na użycie filtrowania? poniższe działa całkiem dobrze nawet z [AnyObject].

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

}
valvoline
źródło
2

Istnieje inna możliwość usunięcia elementu z tablicy bez możliwego niebezpiecznego użycia, ponieważ ogólny typ obiektu do usunięcia nie może być taki sam jak typ tablicy. Korzystanie z opcji nie jest również idealnym sposobem, ponieważ są one bardzo wolne. Można więc użyć zamknięcia, które jest już używane na przykład podczas sortowania tablicy.

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

Gdy rozszerzasz Arrayklasę za pomocą tej funkcji, możesz usunąć elementy, wykonując następujące czynności:

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

Jednak możesz nawet usunąć element tylko wtedy, gdy ma ten sam adres pamięci ( AnyObjectoczywiście tylko dla klas zgodnych z protokołem):

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'

Dobrą rzeczą jest to, że możesz określić parametr do porównania. Na przykład, gdy masz tablicę tablic, możesz określić zamknięcie równości jako, { $0.count == $1.count }a pierwsza tablica o tym samym rozmiarze, co tablica do usunięcia, zostanie usunięta z tablicy.

Możesz nawet skrócić wywołanie funkcji mutating func removeFirst(equality: (Element) -> Bool) -> Bool, używając funkcji as , a następnie zamienić ocenę if na equality(item)i wywołać funkcję array.removeFirst({ $0 == "Banana" })na przykład.

borchero
źródło
Ponieważ ==jest to funkcja, możesz ją również nazwać w ten sposób dla dowolnego typu implementującego ==(takiego jak String, Int itp.):array.removeFirst("Banana", equality:==)
Aviel Gross
@AvielGross Myślę, że to nowość w Swift 2 - jeśli chcesz, możesz odpowiednio edytować odpowiedź
borchero
2

Nie ma potrzeby przedłużania:

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
źródło
1

Używanie indexOfzamiast forlub 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
źródło
1

Może nie zrozumiałem pytania.

Dlaczego to nie zadziała?

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
źródło
0

W końcu skończyłem z następującym kodem.

extension Array where Element: Equatable {

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

}
Kaz Yoshikawa
źródło
0

Udało mi się usunąć [String:AnyObject]z tablicy [[String:AnyObject]], implementując liczbę poza pętlą for do reprezentowania indeksu od .findi .filternie są zgodne z [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
źródło
-1

Wdrożenie w 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
źródło
-4

Udało mi się to zrobić z:

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!)
        }
    }
}
Bałwan
źródło
Porównanie if(index)jest nieważne
juanjo