当前位置: 动力学知识库 > 问答 > 编程问答 >

ios - Array extension to remove object by value

问题描述:

extension Array {

func removeObject<T where T : Equatable>(object: T) {

var index = find(self, object)

self.removeAtIndex(index)

}

}

However, I get an error on var index = find(self, object)

'T' is not convertible to 'T'

I also tried with this method signature: func removeObject(object: AnyObject), however, I get the same error:

'AnyObject' is not convertible to 'T'

What is the proper way to do this?

网友答案:

As of Swift 2, this can be achieved with a protocol extension method. removeObject() is defined as a method on all types conforming to RangeReplaceableCollectionType (in particular on Array) if the elements of the collection are 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)
        }
    }
}

Example:

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

Update for Swift 2 / Xcode 7 beta 2: As Airspeed Velocity noticed in the comments, it is now actually possible to write a method on a generic type that is more restrictive on the template, so the method could now actually be defined as an extension of Array:

extension Array where Element : Equatable {

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

The protocol extension still has the advantage of being applicable to a larger set of types.

Update for 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)
        }
    }
}
网友答案:

You cannot write a method on a generic type that is more restrictive on the template.

NOTE: as of Swift 2.0, you can now write methods that are more restrictive on the template. If you have upgraded your code to 2.0, see other answers further down for new options to implement this using extensions.

The reason you get the error 'T' is not convertible to 'T' is that you are actually defining a new T in your method that is not related at all to the original T. If you wanted to use T in your method, you can do so without specifying it on your method.

The reason that you get the second error 'AnyObject' is not convertible to 'T' is that all possible values for T are not all classes. For an instance to be converted to AnyObject, it must be a class (it cannot be a struct, enum, etc.).

Your best bet is to make it a function that accepts the array as an argument:

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

Or instead of modifying the original array, you can make your method more thread safe and reusable by returning a copy:

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

As an alternative that I don't recommend, you can have your method fail silently if the type stored in the array cannot be converted to the the methods template (that is equatable). (For clarity, I am using U instead of T for the method's template):

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 To overcome the silent failure you can return the success as a 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
网友答案:

briefly and concisely:

func removeObject<T : Equatable>(object: T, inout fromArray array: [T]) 
{
    var index = find(array, object)
    array.removeAtIndex(index!)
}
网友答案:

After reading all the above, to my mind the best answer is:

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

Sample:

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

Swift 2 (xcode 7b4) array extension:

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

Sample:

var myArray = ["Dog", "Cat", "Ant", "Fish", "Cat"]
myArray = myArray.arrayRemovingObject("Cat" )
网友答案:

With using protocol extensions 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)
        }
    }
}
网友答案:

With protocol extensions you can do this,

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

Same functionality for 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)
        }
    }
}

But if a class implements Equatable it becomes ambiguous and the compiler gives an throws an error.

网友答案:

what about to use filtering? the following works quite well even with [AnyObject].

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

}
网友答案:

There is another possibility of removing an item from an array without having possible unsafe usage, as the generic type of the object to remove cannot be the same as the type of the array. Using optionals is also not the perfect way to go as they are very slow. You could therefore use a closure like it is already used when sorting an array for example.

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

When you extend the Array class with this function you can remove elements by doing the following:

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

However you could even remove an element only if it has the same memory address (only for classes conforming to AnyObject protocol, of course):

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'

The good thing is, that you can specify the parameter to compare. For example when you have an array of arrays, you can specify the equality closure as { $0.count == $1.count } and the first array having the same size as the one to remove is removed from the array.

You could even shorten the function call by having the function as mutating func removeFirst(equality: (Element) -> Bool) -> Bool, then replace the if-evaluation with equality(item) and call the function by array.removeFirst({ $0 == "Banana" }) for example.

网友答案:

Using indexOf instead of a for or 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
          }
       }
   }   
}
网友答案:

I finally ended up with following code.

extension Array where Element: Equatable {

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

}
网友答案:

I managed to remove a [String:AnyObject] from an array [[String:AnyObject]] by implementing a count outside of a for loop to represent the index since .find and .filter are not compatible with [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
}
网友答案:

Implementation 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
    }
  }
}
网友答案:

I was able to get it working with:

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!)
        }
    }
}
分享给朋友:
您可能感兴趣的文章:
随机阅读: