160
votes

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.

16
This could be what you are looking for: Find Object with Property in Array. - Martin R
why not convert to nsdictionary and search for - longbow
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. - Swift Soda

16 Answers

97
votes

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]
}
339
votes

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

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]
}
38
votes

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)

21
votes

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

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

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")
}
8
votes

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]
}
2
votes

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.

2
votes

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

import Foundation
class Model: NSObject {  }
2
votes

Swift 3

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

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.

1
votes

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

For Swift 3,

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

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

import Dollar

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

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.