Extensão de matriz para remover o objeto por valor

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

No entanto, recebo um erro ao var index = find(self, object)

'T' não é conversível em 'T'

Eu também tentei com esta assinatura de método: func removeObject(object: AnyObject)no entanto, recebo o mesmo erro:

'AnyObject' não é conversível em 'T'

Qual é a maneira correta de fazer isso?

Boneco de neve
fonte
Tente remover a T wheredeclaração do seu método. Tão apenas func removeObject<T: Equatable>. Esta questão está relacionada: stackoverflow.com/questions/24091046/…
ahruss

Respostas:

165

A partir do Swift 2 , isso pode ser alcançado com um método de extensão de protocolo . removeObject()é definido como um método em todos os tipos em conformidade com RangeReplaceableCollectionType(em particular em Array) se os elementos da coleção forem 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)
        }
    }
}

Exemplo:

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

Atualização para o Swift 2 / Xcode 7 beta 2: Como observou a velocidade do ar nos comentários, agora é possível escrever um método em um tipo genérico mais restritivo no modelo, para que o método agora possa ser definido como uma extensão de Array:

extension Array where Element : Equatable {

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

A extensão do protocolo ainda tem a vantagem de ser aplicável a um conjunto maior de tipos.

Atualização para o 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
fonte
1
Perfeito, você precisa amar Swift (2). Eu realmente gosto de como com o tempo mais as coisas ficam possível e material fica simplificado
Kametrixom
1
Bom ponto, de muitas maneiras, o fato de que a resposta ainda é tecnicamente correta, não é mais idiomática, é ainda pior - as pessoas virão, lerão a resposta, acharão que uma função livre é a maneira certa de resolvê-la, já que é uma resposta altamente classificada . Cenário bem feio. Será publicado na meta.
Velocidade da velocidade do ar
1
@AirspeedVelocity: Uau, eu senti falta disso. Está coberto nas notas de versão?
Martin R
1
Se você deseja a mesma funcionalidade que ObjC (ou seja, remove todos os objetos de correspondência, em vez de apenas o primeiro um), você pode mudar "se" a "enquanto"
powertoold
2
A versão Swift 3 é ótima, mas eu renomearia sua declaração levemente remove(object: Element)para cumprir as diretrizes de design da API Swift e evitar verbosidade. Enviei uma edição refletindo isso.
swiftcode
66

Você não pode escrever um método em um tipo genérico mais restritivo no modelo.

NOTA : a partir Swift 2.0, agora você pode escrever métodos que são mais restritivas no modelo. Se você atualizou seu código para 2.0, consulte outras respostas mais abaixo para novas opções para implementar isso usando extensões.

O motivo pelo qual você obteve o erro 'T' is not convertible to 'T'é que, na verdade, você está definindo um novo T no seu método que não está relacionado ao T. original. Se você deseja usar o T no seu método, pode fazê-lo sem especificá-lo no seu método.

A razão pela qual você obtém o segundo erro 'AnyObject' is not convertible to 'T'é que nem todos os valores possíveis para T são todas as classes. Para que uma instância seja convertida em AnyObject, ela deve ser uma classe (não pode ser uma estrutura, enumeração etc.).

Sua melhor aposta é torná-la uma função que aceite a matriz como argumento:

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

Ou, em vez de modificar a matriz original, você pode tornar seu método mais seguro e reutilizável, retornando uma cópia:

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

Como uma alternativa que eu não recomendo, você pode fazer com que seu método falhe silenciosamente se o tipo armazenado na matriz não puder ser convertido no modelo de métodos (que é equivalente). (Para maior clareza, estou usando U em vez de T para o modelo do método):

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]

Editar Para superar a falha silenciosa, você pode retornar o sucesso como um 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
drewag
fonte
Confira minha resposta aqui: stackoverflow.com/a/24939242/458960 Por que posso fazer isso dessa maneira e não usar o findmétodo?
24414 Boneco de neve
Seu método é suscetível a falhas de tempo de execução. Com a minha função, o compilador impedirá que isso aconteça.
drewag
1
@Isuru Este método funciona com qualquer objeto que implemente o Equatableprotocolo. O UIView faz isso sim, ele irá funcionar com o UIViews
drewag 26/10
4
Uau, escrevendo um loop for para remover um elemento, de volta aos anos 90!
Zorayr 28/03
5
Na última rápida. enumerate(self)tem que consertar paraself.enumerate()
TomSawyer
29

breve e concisa:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
János
fonte
2
Isso é legal. Claro que isso também pode ser feito sem o inout. Mesmo com o inoutintacto, alguém poderia usar array = array.filter() { $0 != object }, eu acho.
Dan Rosenstark
11
Esteja ciente de usar o índice de força desembrulhada, que pode ser nulo. Altere para "se deixar ind = índice {array.removeAtIndex (ind)}"
HotJard 26/04
17

Depois de ler todas as opções acima, na minha opinião a melhor resposta é:

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

Amostra:

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

Extensão da matriz Swift 2 (xcode 7b4):

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

Amostra:

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

Atualização do Swift 3.1

Voltamos a isso agora que o Swift 3.1 foi lançado. Abaixo está uma extensão que fornece variantes exaustivas, rápidas, mutantes e criadoras.

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

Amostras:

// 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"]
dezembro
fonte
isso não retorna uma instância completamente nova da matriz?
Pxpgraphics
Sim. É um estilo mais funcional. YMMV.
dezembro
Costumo concordar com o estilo funcional, exceto, neste caso, quando a filterfunção já lida com essa funcionalidade para você. Isso parece duplicar a funcionalidade. Mas uma boa resposta, no entanto:]]
pxpgraphics 20/03
13

Com extensões de protocolo, você pode fazer isso,

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

A mesma funcionalidade para as 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)
        }
    }
}

Mas se uma classe implementa Equatable, torna-se ambígua e o compilador gera um erro.

Sri Krishna Paritala
fonte
1
Estou recebendo umBinary operator '===' cannot be applied to two elements of type '_' and 'Element'
sapato
6

Com o uso de extensões de protocolo no 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
fonte
4

que tal usar a filtragem? o seguinte funciona muito bem, mesmo com [AnyObject].

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

}
valvoline
fonte
2

Há outra possibilidade de remover um item de uma matriz sem ter um uso inseguro possível, pois o tipo genérico do objeto a ser removido não pode ser o mesmo que o tipo da matriz. O uso de opcionais também não é o caminho perfeito, pois eles são muito lentos. Portanto, você pode usar um fechamento como ele já é usado ao classificar uma matriz, por exemplo.

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

Ao estender a Arrayclasse com esta função, você pode remover elementos fazendo o seguinte:

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

No entanto, você pode remover um elemento apenas se ele tiver o mesmo endereço de memória (apenas para classes em conformidade com o AnyObjectprotocolo, é claro):

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'

O bom é que você pode especificar o parâmetro para comparar. Por exemplo, quando você tem uma matriz de matrizes, pode especificar o fechamento de igualdade como { $0.count == $1.count }e a primeira matriz com o mesmo tamanho que a remover é removida da matriz.

Você pode até encurtar a chamada de função tendo a função como mutating func removeFirst(equality: (Element) -> Bool) -> Bool, então substitua a avaliação se por equality(item)e chame a função por, array.removeFirst({ $0 == "Banana" })por exemplo.

borchero
fonte
Como ==é uma função, você também pode chamá-lo assim para qualquer tipo que implemente ==(como String, Int etc.):array.removeFirst("Banana", equality:==)
Aviel Gross
@AvielGross isto é novo no Swift 2 eu acho - sinta-se livre para editar a resposta de acordo, se você quiser
borchero
2

Não há necessidade de estender:

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

Usando em indexOfvez de um 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
fonte
1

Talvez eu não tenha entendido a pergunta.

Por que isso não funcionaria?

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

Finalmente acabei com o seguinte código.

extension Array where Element: Equatable {

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

}
Kaz Yoshikawa
fonte
0

Eu consegui remover a [String:AnyObject]de uma matriz [[String:AnyObject]]implementando uma contagem fora de um loop for para representar o índice desde .finde .filternão é compatível [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
fonte
-1

Implementação no 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
fonte
-4

Consegui fazê-lo funcionar com:

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!)
        }
    }
}
Boneco de neve
fonte
A comparação if(index)é inválida
juanjo 21/11