199

Does Swift have something like _.findWhere in Underscore.js?

I have an array of structs of type T and would like to check if array contains a struct object whose name property is equal to Foo.

Tried to use find() and filter() but they only work with primitive types, e.g. String or Int. Throws an error about not conforming to Equitable protocol or something like that.

3
  • This could be what you are looking for: Find Object with Property in Array. Commented Feb 25, 2015 at 19:34
  • why not convert to nsdictionary and search for Commented Feb 25, 2015 at 20:12
  • 3
    I believe find is no longer available in Swift 2.0.. I was converting some 1.2 code to Swift 2.0 and it said to use IndexOf instead. Commented Aug 22, 2015 at 18:37

16 Answers 16

429

SWIFT 5

Check if the element exists

if array.contains(where: {$0.name == "foo"}) {
   // it exists, do something
} else {
   //item could not be found
}

Get the element

if let foo = array.first(where: {$0.name == "foo"}) {
   // do something with foo
} else {
   // item could not be found
}

Get the element and its offset

if let foo = array.enumerated().first(where: {$0.element.name == "foo"}) {
   // do something with foo.offset and foo.element
} else {
   // item could not be found
}

Get the offset

if let fooOffset = array.firstIndex(where: {$0.name == "foo"}) {
    // do something with fooOffset
} else {
    // item could not be found
}
Sign up to request clarification or add additional context in comments.

8 Comments

Nice Swift-style answer!
Thanks you are my saver this has cleaned my code a lot
how to check multiple conditions say i need to check if the array $0.name == "foo" do one operation and $0.name == "boo" do another operation
This helped me more than the accepted answer in 2020.
How can I check $0.name contains string "foo"? Can anyone give the syntax for that
|
161

You can use the index method available on Array with a predicate (see Apple's documentation here).

func index(where predicate: (Element) throws -> Bool) rethrows -> Int?

For your specific example this would be:

Swift 5.0

if let i = array.firstIndex(where: { $0.name == "Foo" }) {
    return array[i]
}

Swift 3.0

if let i = array.index(where: { $0.name == Foo }) {
    return array[i]
}

Swift 2.0

if let i = array.indexOf({ $0.name == Foo }) {
    return array[i]
}

4 Comments

Yes this works with swift 2.0 only. Sorry should have mentioned it .
@user3799504 meanning of $0 ?
$0 is shorthand for the first argument to the closure. In this case it refers to self.generator.element or each element of the array. Please see : developer.apple.com/library/ios/documentation/Swift/Conceptual/…
How about Swift 3?
102

FWIW, if you don't want to use custom function or extension, you can:

let array = [ .... ]
if let found = find(array.map({ $0.name }), "Foo") {
    let obj = array[found]
}

This generates name array first, then find from it.

If you have huge array, you might want to do:

if let found = find(lazy(array).map({ $0.name }), "Foo") {
    let obj = array[found]
}

or maybe:

if let found = find(lazy(array).map({ $0.name == "Foo" }), true) {
    let obj = array[found]
}

4 Comments

This is even better. I will mark this as answer since it looks simpler overall and it does not require creating a custom function.
As of Swift 2.0 you can use: array.indexOf({$0.name == "Foo"})
As of Swift 3.0 you can use: array.first(where:{$0.name == "Foo"}) if you need the object
How can I check $0.name contains string "foo"? answers are about exact matching. I need contains string. Can anyone give the syntax for that
42

Swift 3

If you need the object use:

array.first{$0.name == "Foo"}

(If you have more than one object named "Foo" then first will return the first object from an unspecified ordering)

3 Comments

This is nice, thank you! Can be written like this also: array.first {$0.name == "Foo"}
In Swift3 it must be array.first(where: {$0.name == "Foo"})
With Daniel's note, this is THE correct, best answer for Swift 3. Don't use map, filter for this purpose; they iterate over entire collections, which can be hugely wasteful.
24

You can filter the array and then just pick the first element, as shown in Find Object with Property in Array.

Or you define a custom extension

extension Array {

    // Returns the first element satisfying the predicate, or `nil`
    // if there is no matching element.
    func findFirstMatching<L : BooleanType>(predicate: T -> L) -> T? {
        for item in self {
            if predicate(item) {
                return item // found
            }
        }
        return nil // not found
    }
}

Usage example:

struct T {
    var name : String
}

let array = [T(name: "bar"), T(name: "baz"), T(name: "foo")]

if let item = array.findFirstMatching( { $0.name == "foo" } ) {
    // item is the first matching array element
} else {
    // not found
}

In Swift 3 you can use the existing first(where:) method (as mentioned in a comment):

if let item = array.first(where: { $0.name == "foo" }) {
    // item is the first matching array element
} else {
    // not found
}

5 Comments

In terms of efficiency how does this compare with array.lazy.filter( predicate ).first ? How efficient is .lazy for small arrays?
@PatNiemeyer: I don't know, you would have to measure the performance and compare.
@PatNiemeyer the solution above will most certainly be more efficient even though the difference won't be probably big. 1. Complexity of the filter is always O(n) whereas in the findFirstMatching it's only in the worst scenario (when the element you are looking for is the last or not in the array at all). 2. filter creates a completely new array of filtered elements while the findFirstMatching just returns the requested element.
In Swift 3, I'm getting the errors Inheritance from non-protocol, non-class type 'Bool' and Use of undeclared type 'T' for this extension method.
@Isuru: That answer was quite old and referred to an old Swift version. In Swift 3 you don't need an custom extension method anymore for that purpose, I have updated the answer accordingly.
24

Swift 3.0

if let index = array.index(where: { $0.name == "Foo" }) {
    return array[index]
}

Swift 2.1

Filtering in object properties is now supported in swift 2.1. You can filter your array based on any value of the struct or class here is an example

for myObj in myObjList where myObj.name == "foo" {
 //object with name is foo
}

OR

for myObj in myObjList where myObj.Id > 10 {
 //objects with Id is greater than 10
}

Comments

13

Swift 4,

Another way to achieve this using filter function,

if let object = elements.filter({ $0.title == "title" }).first {
    print("found")
} else {
    print("not found")
}

Comments

9

Swift 3

you can use index(where:) in Swift 3

func index(where predicate: @noescape Element throws -> Bool) rethrows -> Int?

example

if let i = theArray.index(where: {$0.name == "Foo"}) {
    return theArray[i]
}

1 Comment

Is there a method in swift 3 where you can find the indices of a sublist of array items that fulfill the condition $0.name == "Foo"?
3

Swift 2 or later

You can combine indexOf and map to write a "find element" function in a single line.

let array = [T(name: "foo"), T(name: "Foo"), T(name: "FOO")]
let foundValue = array.indexOf { $0.name == "Foo" }.map { array[$0] }
print(foundValue) // Prints "T(name: "Foo")"

Using filter + first looks cleaner, but filter evaluates all the elements in the array. indexOf + map looks complicated, but the evaluation stops when the first match in the array is found. Both the approaches have pros and cons.

Comments

3

Another way to get access to array.index(of: Any) is by declaring your object

import Foundation
class Model: NSObject {  }

Comments

3

Swift 3

if yourArray.contains(item) {
   //item found, do what you want
}
else{
   //item not found 
   yourArray.append(item)
}

Comments

2

Use contains:

var yourItem:YourType!
if contains(yourArray, item){
    yourItem = item
}

Or you could try what Martin pointed you at, in the comments and give filter another try: Find Object with Property in Array.

4 Comments

Will that only return a boolean? I need to get the object too, not just check if is in the array.
This assume item is of the same type as item in the array. However, all I have is just a title from view.annotation.title. I need to compare items in the array by this title.
Something like if contains(yourArray, view.annotation.title) { // code goes here }.
Martin showed you another way in the comments. Check the link he provided.
2

Swift 3:

You can use Swifts built in functionality to find custom objects in an Array.

First you must make sure your custom object conforms to the: Equatable protocol.

class Person : Equatable { //<--- Add Equatable protocol
    let name: String
    var age: Int

    init(name: String, age: Int) {
        self.name = name
        self.age = age
    }

    //Add Equatable functionality:
    static func == (lhs: Person, rhs: Person) -> Bool {
        return (lhs.name == rhs.name)
    }
}

With Equatable functionality added to your object , Swift will now show you additional properties you can use on an array:

//create new array and populate with objects:
let p1 = Person(name: "Paul", age: 20)
let p2 = Person(name: "Mike", age: 22)
let p3 = Person(name: "Jane", age: 33)
var people = [Person]([p1,p2,p3])

//find index by object:
let index = people.index(of: p2)! //finds Index of Mike

//remove item by index:
people.remove(at: index) //removes Mike from array

Comments

2

For Swift 3,

let index = array.index(where: {$0.name == "foo"})

Comments

1

Use Dollar which is Lo-Dash or Underscore.js for Swift:

import Dollar

let found = $.find(array) { $0.name == "Foo" }

Comments

1

For example, if we had an array of numbers:

let numbers = [2, 4, 6, 8, 9, 10]

We could find the first odd number like this:

let firstOdd = numbers.index { $0 % 2 == 1 }

That will send back 4 as an optional integer, because the first odd number (9) is at index four.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.