Daij-Djan
Daij-Djan

Reputation: 50099

How do I do indexOfObject or a proper containsObject

With an array: How do I do indexOfObject or a proper containsObject?

I mean I know I could just bridge the Array to NSArray and do it there ^^
But there must be a 'native' way of doing this

P.S. for the containsObject I guess I could filter the array too but for indexOf?

Upvotes: 20

Views: 21182

Answers (8)

matt
matt

Reputation: 535304

You can use the built-in find, and thus avoid bridging to Objective-C — but only if your element type is Equatable. (If it isn't Equatable, you can make it so with a comparison function and an extension.)

Example:

func == (lhs:Piece,rhs:Piece) -> Bool {
    return lhs.val == rhs.val
}

class Piece:Equatable,Printable {
    var val : Int
    var description : String { return String(val) }
    init (_ v:Int) {
        val = v
    }
}

Now you can call find(arr,p) where arr is an Array<Piece> and p is a Piece.

Once you have this, you can develop utilities based on it. For example, here's a global function to remove an object from an array without bridging to Objective-C:

func removeObject<T:Equatable>(inout arr:Array<T>, object:T) -> T? {
    if let found = find(arr,object) {
        return arr.removeAtIndex(found)
    }
    return nil
}

And test it like this:

var arr = [Piece(1), Piece(2), Piece(3)]
removeObject(&arr,Piece(2))
println(arr)

You can do this for NSObject subclasses too. Example:

func == (v1:UIView, v2:UIView) -> Bool {
    return v1.isEqual(v2)
}
extension UIView : Equatable {}

Now you can call find on an Array of UIView. It's sort of a pain in the butt, though, having to do this for every single class where you want to be able to use find on an Array of that class. I have filed an enhancement request with Apple requesting that all NSObject subclasses be considered Equatable and that == should fall back on isEqual: automatically.

EDIT Starting in Seed 3, this is automatic for UIView and other NSObject classes. So find now just works for them.

EDIT 2 Starting in Swift 2.0, indexOf will exist as a method:

let s = ["Manny", "Moe", "Jack"]
let ix = s.indexOf("Moe") // 1

Alternatively, it takes a function that returns Bool:

let ix2 = s.indexOf {$0.hasPrefix("J")} // 2

Again, this works only on collections of Equatable, since obviously you cannot locate a needle in a haystack unless you have a way of identifying a needle when you come to it.

EDIT 3 Swift 2.0 also introduces protocol extensions. This means I can rewrite my global function removeObject as a method!

For example:

extension RangeReplaceableCollectionType where Generator.Element : Equatable {
    mutating func removeObject(object:Self.Generator.Element) {
        if let found = self.indexOf(object) {
            self.removeAtIndex(found)
        }
    }
}

Since Array adopts RangeReplaceableCollectionType, now I can write code like this:

var arr = [Piece(1), Piece(2), Piece(3)]
arr.removeObject(Piece(2))

Oh, happy day!

Upvotes: 44

rmooney
rmooney

Reputation: 6229

One other option is to use filter:

haystack.filter({$0 == needle}).count > 0

This checks to see if array haystack contains object needle.

Upvotes: 2

phatmann
phatmann

Reputation: 18493

If your array elements are objects and you want to find an identical object in that array, you can use this function:

func findObject<C: CollectionType where C.Generator.Element: AnyObject>(domain: C, value: C.Generator.Element) -> Int? {
    for (index, element) in enumerate(domain) {
        if element === value {
            return index
        }
    }

    return nil
}

Upvotes: -2

Max MacLeod
Max MacLeod

Reputation: 26652

Apple provide an example of exactly this in the The Swift Programming Language book. Specifically, see the section on Type Constraints in Action (p621 in the iBook).

func findIndex<T: Equatable>(array: [T], valueToFind: T) -> Int? {
    for (index, value) in enumerate(array) {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

Everything depends upon your type implementing Equatable.

The Swift Programming Language covers that and explains how to implement that protocol:

“The Swift standard library defines a protocol called Equatable, which requires any conforming type to implement the equal to operator (==) and the not equal to operator (!=) to compare any two values of that type. ”

NSHipster has a couple of relevant posts on this subject:

Swift Default Protocol Implementations Swift Comparison Protocols

I also found this answer very useful in implementing Equatable:

How do I implement Swift's Comparable protocol?

Alhough it mentions Comparable, Equatable is a subset and the explanation is good.

Excerpts above from: Apple Inc. “The Swift Programming Language.” iBooks. https://itun.es/gb/jEUH0.l

Upvotes: 2

Daij-Djan
Daij-Djan

Reputation: 50099

As I was told, this isn't available yet / I have to bridge it to NSArray

I don't like this and it feels dirty so I went and did this in an extension. that way it hides the usage of NSArray and allows apple to provide it later

import Foundation

extension Array {
    func contains(object:AnyObject!) -> Bool {
        if(self.isEmpty) {
            return false
        }
        let array: NSArray = self.bridgeToObjectiveC();

        return array.containsObject(object)
    }

    func indexOf(object:AnyObject!) -> Int? {
        var index = NSNotFound
        if(!self.isEmpty) {
            let array: NSArray = self.bridgeToObjectiveC();
            index = array.indexOfObject(object)
        }
        if(index == NSNotFound) {
            return Optional.None;
        }
        return index
    }

    //#pragma mark KVC

    func getKeyPath(keyPath: String!) -> AnyObject! {
        return self.bridgeToObjectiveC().valueForKeyPath(keyPath);
    }
}

https://gist.github.com/Daij-Djan/9d1c4b1233b4017f3b67

Upvotes: 4

Maximilian Litteral
Maximilian Litteral

Reputation: 3089

Its actually able to be done in Swift. To get the index use find(YourArray, ObjectToFind)

Upvotes: 9

Ben Gottlieb
Ben Gottlieb

Reputation: 85532

It appears that not all of the toll-free bridging from NS/CF space is in place. However, if you declare your array as an NSArray, it works fine:

let     fruits: NSArray = [ "apple", "orange", "tomato (really?)" ]
let     index = fruits.indexOfObject("orange")

println("Index of Orange: \(index)")

Upvotes: 1

Leandros
Leandros

Reputation: 16825

You can't. That's why NSArray is still there. However, the Apple documentation reads as follows about String and NSString:

Swift’s String type is bridged seamlessly to Foundation’s NSString class. If you are working with the Foundation framework in Cocoa or Cocoa Touch, the entire NSString API is available to call on any String value you create, in addition to the String features described in this chapter. You can also use a String value with any API that requires an NSString instance.

Following that approach, the NSArray API should be available on Array, but it isn't because the native Swift Array is a primitive (most likely a struct or similar), so you have to "convert" it to an NSArray.

Upvotes: 1

Related Questions