Related
If I have a collection
let initial = [ "a", "b", "c", "d", "e" ]
and I wanted to move an item from that collection to the start (but keep the ordering of the other items intact)
let final = initial.placeFirst { $0 == "b" }
assert(final == [ "b", "a", "c", "d", "e" ])
What would be the best way to implement placeFirst?
My example has the elements as Equatable - that's just to make the question readable, it's sadly not the case in real life, hence a predicate passed into placeFirst which will return true for the item I want at the start.
For my use case there should only be one item which matches the predicate - if more than one matches then putting any (or some, or all) of the matching elements at the start is fine.
I have a few ideas, but it seems like the kind of problem there would be a really neat solution which uses bits of Collection/Sequence I'm not aware of yet.
PS I do realize how much this sounds like a homework question - I promise it's not :)
A possible implementation as a mutating method on RangeReplaceableCollection (Swift 3):
extension RangeReplaceableCollection {
mutating func placeFirst(where predicate: (Iterator.Element) -> Bool) {
if let index = index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
Example:
var array = [ "a", "b", "c", "d", "e" ]
array.placeFirst(where: { $0 == "b" })
print(array) // ["b", "a", "c", "d", "e"]
Similar as in How do I shuffle an array in Swift? you can add a
non-mutating method taking an arbitrary sequence and returning an array:
extension Sequence {
func placingFirst(where predicate: (Iterator.Element) -> Bool) -> [Iterator.Element] {
var result = Array(self)
result.placeFirst(where: predicate)
return result
}
}
Example:
let initial = [ "a", "b", "c", "d", "e" ]
let final = initial.placingFirst { $0 == "b" }
print(final) // ["b", "a", "c", "d", "e"]
A possible implementation as a pair of mutating methods on MutableCollection (doesn't require the resizing of the collection):
extension MutableCollection {
mutating func placeFirst(from index: Index) {
var i = startIndex
while i < index {
swap(&self[i], &self[index]) // in Swift 4: swapAt(i, index)
formIndex(after: &i)
}
}
// in Swift 4, remove Iterator.
mutating func placeFirst(where predicate: (Iterator.Element) throws -> Bool) rethrows {
var i = startIndex
while i < endIndex {
if try predicate(self[i]) {
placeFirst(from: i)
}
formIndex(after: &i)
}
}
}
var initial = ["a", "b", "c", "d", "e", "c", "q"]
initial.placeFirst(where: { $0 == "c" })
print(initial) // ["c", "c", "a", "b", "d", "e", "q"]
In placeFirst(from:), we just take a single index, and swap all the elements from the start index up to the desired index, effectively placing the element at the given index at the start, and "shifting" the remaining elements up.
Then in the predicate version, placeFirst(where:), we iterate through and check the predicate against all the indices of the collection, calling onto placeFirst(from:) if we find a match.
And as Martin says, a non-mutating variant for all sequences can be created easily by first constructing an Array:
extension Sequence {
// in Swift 4, remove Iterator.
func placingFirst(
where predicate: (Iterator.Element) throws -> Bool
) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirst(where: predicate)
return result
}
}
let initial = ["a", "b", "c", "d", "e", "c", "q"]
let final = initial.placingFirst(where: { $0 == "c" })
print(final) // ["c", "c", "a", "b", "d", "e", "q"]
In order to benchmark against Martin's implementation, I changed the implementation of my placeFirst(where:) to only consider the first element that the predicate matches, such that both implementations short-circuit:
extension MutableCollection {
mutating func placeFirstSwap(from index: Index) {
var i = startIndex
while i < index {
swapAt(i, index)
formIndex(after: &i)
}
}
mutating func placeFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
placeFirstSwap(from: index)
}
}
}
extension RangeReplaceableCollection {
mutating func placeFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows {
if let index = try index(where: predicate) {
insert(remove(at: index), at: startIndex)
}
}
}
extension Sequence {
func placingFirstInsertRemove(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstInsertRemove(where: predicate)
return result
}
func placingFirstSwap(where predicate: (Iterator.Element) throws -> Bool) rethrows -> [Iterator.Element] {
var result = Array(self)
try result.placeFirstSwap(where: predicate)
return result
}
}
Then, with the following setup in a Swift 4 release build:
import Foundation
let a = Array(0 ... 50_000_000)
let i = 33_000_000
print("pivot \(100 * Double(i) / Double(a.count - 1))% through array")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
print("---")
do {
let date = Date()
let final = a.placingFirstInsertRemove(where: { $0 == i })
print(final.count, "Martin's:", Date().timeIntervalSince(date))
}
do {
let date = Date()
let final = a.placingFirstSwap(where: { $0 == i })
print(final.count, "Hamish's:", Date().timeIntervalSince(date))
}
When i is around 33_000_000, both implementations appear to have similar performance:
pivot 66.0% through array
50000001 Martin's: 0.344986021518707
50000001 Hamish's: 0.358841001987457
---
50000001 Martin's: 0.310263991355896
50000001 Hamish's: 0.313731968402863
With Martin's performing slightly better for values of i over this, e.g with i = 45_000_000:
pivot 90.0% through array
50000001 Martin's: 0.35604602098465
50000001 Hamish's: 0.392504990100861
---
50000001 Martin's: 0.321934998035431
50000001 Hamish's: 0.342424035072327
and mine performing slightly better for values of i less than this, e.g with i = 5_000_000:
pivot 10.0% through array
50000001 Martin's: 0.368523001670837
50000001 Hamish's: 0.271382987499237
---
50000001 Martin's: 0.289749026298523
50000001 Hamish's: 0.261726975440979
In all of these results, the second pair is generally more reliable, as both should benefit from branch prediction done by the first run.
I'm using Swift 3.0 and have this code for searching an item in an array as an extension of String type:
extension String {
func equal(compareToArray : [String]) -> Bool {
for s in compareToArray {
if self == s {
return true
}
}
return false
}
}
It runs fine, but my question is, can I do it better (shorter/more simple or faster)?
Okay, another similar sample:
func contains(compareToArray : [String]) -> Bool {
for s in compareToArray {
if self.contains(s) {
return true
}
}
return false
}
Shorter, simpler, faster
let compareToArray = ["foo", "bar", "baz"]
compareToArray.contains("bar")
Edit:
According to your second example
!compareToArray.filter{ $0.contains("oo") }.isEmpty
compareToArray.index(where: {$0.contains("oo")}) != nil
If you want to check whether an element belong to an Array, in Swift 3.0 this is a better way:
use :
array.index(of: element) -> Int?
Ex:
let myArray = ["a", "b", "c"]
let needle = "b"
if let pos = myArray.index(of: needle') {
print("\(needle) is in array at position : \(pos)"
} else {
print("It's not in array")
}
Im learning swift and am having a problem Iterating through an array.
Here is what I'm trying to do:
func orderStringByOccurence(stringArray: [String]) -> [String: Int]{
var stringDictionary: [String: Int] = [:]
for i in 0...stringArray.count {
if stringDictionary[stringArray[i]] == nil {
stringDictionary[stringArray[i]] = 1
stringDictionary
} else {
stringDictionary[stringArray[i]]! += 1
}
}
return stringDictionary
}
I don't get an error until I try to call this function. Then I get this error:
EXC_BAD_INSTRUCTION (code=EXC_1386_INVOP, subcode=0x0)
I have tried debugging and found that i get the same error when i try this:
for i in 0...arrayFromString.count{
print(arrayFromString[i])
}
So how do I iterate through this array?
Thanks for helping out a new
You need to change
for i in 0...arrayFromString.count
to
for i in 0..<arrayFromString.count
As it is now, you iterate through the array and then one past the end.
You can also use a different style of for loop, which is perhaps a little better:
func orderStringByOccurence(stringArray: [String]) -> [String: Int] {
var stringDictionary: [String: Int] = [:]
for string in stringArray {
if stringDictionary[string] == nil {
stringDictionary[string] = 1
} else {
stringDictionary[string]! += 1
}
}
return stringDictionary
}
Also, you can simplify your logic a bit:
for string in stringArray {
stringDictionary[string] = stringDictionary[string] ?? 0 + 1
}
Update - For the sake of completeness, I thought I'd add a reduce example here as well. Note that as of Swift 5.1 return statements in single line functions can be implied (SE-0255).
func orderStringByOccurence(stringArray: [String]) -> [String: Int] {
stringArray.reduce([:]) { result, string in result.merging([string: 1], uniquingKeysWith: +)}
}
A few more approaches:
let array = ["1", "2", "3"]
You can use forEach with trailing closure syntax:
array.forEach { item in
print(item)
}
You can use the $0 shorthand:
array.forEach {
print($0)
}
And if you need the indexes, you can use enumerate():
array.enumerate().forEach { itemTuple in
print("\(itemTuple.element) is at index \(itemTuple.index)")
}
It seems you're out of index. A more swift-like approach would be in my opinion not to use the count but to do range-based.
var stringArray = ["1", "2", "3"]
for string in stringArray
{
print(string)
}
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)
}
}
}
Update for Swift 5:
extension Array where Element: Equatable {
/// Remove first collection element that is equal to the given `object` or `element`:
mutating func remove(element: Element) {
if let index = firstIndex(of: element) {
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" )
Swift 3.1 update
Came back to this now that Swift 3.1 is out. Below is an extension which provides exhaustive, fast, mutating and creating variants.
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
}
}
Samples:
// 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"]
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.
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)
}
}
}
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})
}
}
Maybe I didn't understand the question.
Why wouldn't this work?
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
No need to extend:
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]
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
}
}
Your problem is T is not related to the type of your array in anyway for example you could have
var array = [1,2,3,4,5,6]
array.removeObject(object:"four")
"six" is Equatable, but its not a type that can be compared to Integer, if you change it to
var array = [1,2,3,4,5,6]
extension Array where Element : Equatable {
mutating func removeObject(object: Element) {
filter { $0 != object }
}
}
array.removeObject(object:"four")
it now produces an error on calling removeObject for the obvious reason its not an array of strings, to remove 4 you can just
array.removeObject(object:4)
Other problem you have is its a self modifying struct so the method has to be labeled as so and your reference to it at the top has to be a var
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!)
}
}
}
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)
}
}
}
Update for Swift 5:
extension Array where Element: Equatable {
/// Remove first collection element that is equal to the given `object` or `element`:
mutating func remove(element: Element) {
if let index = firstIndex(of: element) {
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" )
Swift 3.1 update
Came back to this now that Swift 3.1 is out. Below is an extension which provides exhaustive, fast, mutating and creating variants.
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
}
}
Samples:
// 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"]
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.
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)
}
}
}
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})
}
}
Maybe I didn't understand the question.
Why wouldn't this work?
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
No need to extend:
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]
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
}
}
Your problem is T is not related to the type of your array in anyway for example you could have
var array = [1,2,3,4,5,6]
array.removeObject(object:"four")
"six" is Equatable, but its not a type that can be compared to Integer, if you change it to
var array = [1,2,3,4,5,6]
extension Array where Element : Equatable {
mutating func removeObject(object: Element) {
filter { $0 != object }
}
}
array.removeObject(object:"four")
it now produces an error on calling removeObject for the obvious reason its not an array of strings, to remove 4 you can just
array.removeObject(object:4)
Other problem you have is its a self modifying struct so the method has to be labeled as so and your reference to it at the top has to be a var
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!)
}
}
}