528

I am trying to find an item index by searching a list. Does anybody know how to do that?

I see there is list.StartIndex and list.EndIndex but I want something like python's list.index("text").

ArunPratap
  • 4,816
  • 7
  • 25
  • 43
Chéyo
  • 9,107
  • 9
  • 27
  • 44

23 Answers23

927

As swift is in some regards more functional than object-oriented (and Arrays are structs, not objects), use the function "find" to operate on the array, which returns an optional value, so be prepared to handle a nil value:

let arr:Array = ["a","b","c"]
find(arr, "c")!              // 2
find(arr, "d")               // nil

Use firstIndex and lastIndex - depending on whether you are looking for the first or last index of the item:

let arr = ["a","b","c","a"]

let indexOfA = arr.firstIndex(of: "a") // 0
let indexOfB = arr.lastIndex(of: "a") // 3
johndpope
  • 5,035
  • 2
  • 41
  • 43
Sebastian Schuth
  • 9,999
  • 1
  • 20
  • 16
  • 37
    Where did you learn about the find function? I can't seem to find any documentation of "find" or any other global functions – Johannes Jun 13 '14 at 17:48
  • 17
    Coming from the OOP world, how am I supposed to find this kind of free floating functions? – Rudolf Adamkovič Sep 11 '14 at 21:00
  • can also be used as `Swift.find(arr, "some value")` without the import. – Shahar Oct 13 '14 at 21:55
  • 6
    Johannes and @Rudolf - use Dash.app. It's an OS X app for browsing documentation. It has a language reference for Swift that has a list of all the free floating functions. Easy to filter and search. Can't live without it. – Bjorn Jan 17 '15 at 06:25
  • 1
    @BjornTipling Too bad Apple can't make a usable documentation browser. – Rudolf Adamkovič Jan 17 '15 at 13:52
  • 4
    FYI: If you're using `indexOf` on an array of structs you defined yourself, your struct has to comply with the `Equatable` protocol. – Matthew Quiros Jul 15 '15 at 07:52
  • Just as a note - in Swift2, there appears to be no special work needed to use this. – solenoid Feb 03 '16 at 02:41
  • When searching Swift 3 arrays which contain instances of `NSObject` with the `index(of:)` method, the old `-isEqual:` method is used for comparison. In a test project I found that the `==` operator is not invoked. Quite surprising. Examples using strings do work though. – bio Oct 08 '16 at 18:34
  • 3
    `arr.index(of: "element")` is not available in Swift 3 – MikeG Jan 13 '17 at 19:12
  • swift 3 example is wrong. index(of:) function not exists in swift 3. – mkeremkeskin Feb 10 '17 at 12:56
  • @sschuth what does item.foo refers to? – ViruMax Apr 09 '17 at 06:54
  • All the answers are assuming object "equality" is used and not object "identity". to use these methods you MUST know and assume the object is of type "String" and use string comparison for finding a match. For that - if you have 3 objects with the value "a" you will always find only the first. The original question was about looking for an actual object. not an object equal to it. – Motti Shneor Apr 26 '17 at 07:37
  • 1
    If you're dealing with class types, you can do `arr.index(where: { $0 === target })` – Uncommon May 25 '17 at 22:47
  • @sschuth you may want to update for Swift 4.1 too :) – bibscy Apr 26 '18 at 07:55
  • 5
    Can we delete the Swift 2 stuff? It's traumatic to see. – Dan Rosenstark Nov 09 '18 at 14:23
  • Swift 4.2, firstIndex(of:) returns optional integer which is supposed to be used with "if let". – Lokesh Purohit Apr 03 '19 at 05:48
  • what is there is one more "a" in between ? how to get that index? – Rajneesh071 Nov 19 '20 at 16:08
267

tl;dr:

For classes, you might be looking for:

let index = someArray.firstIndex{$0 === someObject}

Full answer:

I think it's worth mentioning that with reference types (class) you might want to perform an identity comparison, in which case you just need to use the === identity operator in the predicate closure:


Swift 5, Swift 4.2:

let person1 = Person(name: "John")
let person2 = Person(name: "Sue")
let person3 = Person(name: "Maria")
let person4 = Person(name: "Loner")

let people = [person1, person2, person3]

let indexOfPerson1 = people.firstIndex{$0 === person1} // 0
let indexOfPerson2 = people.firstIndex{$0 === person2} // 1
let indexOfPerson3 = people.firstIndex{$0 === person3} // 2
let indexOfPerson4 = people.firstIndex{$0 === person4} // nil

Note that the above syntax uses trailing closures syntax, and is equivalent to:

let indexOfPerson1 = people.firstIndex(where: {$0 === person1})


Swift 4 / Swift 3 - the function used to be called index

Swift 2 - the function used to be called indexOf

* Note the relevant and useful comment by paulbailey about class types that implement Equatable, where you need to consider whether you should be comparing using === (identity operator) or == (equality operator). If you decide to match using ==, then you can simply use the method suggested by others (people.firstIndex(of: person1)).

Nikolay Suvandzhiev
  • 8,465
  • 6
  • 41
  • 47
  • 7
    This is a useful post for those wondering why .indexOf(x) doesn't work - this solution is unexpected but perfectly obvious in retrospect. – Maury Markowitz Feb 10 '16 at 16:35
  • 2
    Thanks so much for this but it's not obvious to me at all. I looked at the documentation and I really don't understand why would I need a predicate closure when using indexOf on a reference type? It feels like indexOf should already be able to handle reference types on its own. It should know that it's a reference type and not a value type. – Chris Mar 08 '16 at 13:18
  • 7
    If `Person` implemented the `Equatable` protocol, this wouldn't be needed. – paulbailey Mar 29 '16 at 15:26
  • 2
    i'm receiving: `Binary operator '===' cannot be applied to operands of type '_' and 'Post'` , `Post` is my struct... any idea? – David Seek Dec 21 '16 at 17:05
  • @DavidSeek, `structs` (and `enums`) are value types, not reference types. Only reference types (e.g `class`) have identity comparison logic (`===`). Check out the other answers for what to do with `structs` (basically you simply use the `array.index(of: myStruct)`, making sure type of `myStruct` conforms to `Equatable` (`==`)). – Nikolay Suvandzhiev Oct 06 '17 at 09:38
  • thank you @NikolaySuvandzhiev i have already learned that and am having my structs equatable :) but thank you for the explanation – David Seek Oct 06 '17 at 14:54
  • This is should be the accepted answer as it works for both simple array and array of object. – Prabhakar Kasi Jul 16 '20 at 04:28
  • very helpful, firstIndex gives the expected result! – Mihail Salari Oct 27 '20 at 21:18
88

You can filter an array with a closure:

var myList = [1, 2, 3, 4]
var filtered = myList.filter { $0 == 3 }  // <= returns [3]

And you can count an array:

filtered.count // <= returns 1

So you can determine if an array includes your element by combining these:

myList.filter { $0 == 3 }.count > 0  // <= returns true if the array includes 3

If you want to find the position, I don't see fancy way, but you can certainly do it like this:

var found: Int?  // <= will hold the index if it was found, or else will be nil
for i in (0..x.count) {
    if x[i] == 3 {
        found = i
    }
}

EDIT

While we're at it, for a fun exercise let's extend Array to have a find method:

extension Array {
    func find(includedElement: T -> Bool) -> Int? {
        for (idx, element) in enumerate(self) {
            if includedElement(element) {
                return idx
            }
        }
        return nil
    }
}

Now we can do this:

myList.find { $0 == 3 }
// returns the index position of 3 or nil if not found
gwcoffey
  • 5,551
  • 1
  • 18
  • 20
  • For funsies I added another example where I extend the builtin `Array` to have a `find` method that does what you want. I don't know yet if this is a good practice, but it is a neat experiment. – gwcoffey Jun 04 '14 at 04:50
  • 2
    Just want to point out, that you should use ++idx as per docs: "Unless you need the specific behavior of i++, it is recommended that you use ++i and --i in all cases, because they have the typical expected behavior of modifying i and returning the result." – Logan Jun 04 '14 at 05:05
  • Good call. As you were posting this I was revising it to use `enumerate` so it no longer applies, but you're absolutely right. – gwcoffey Jun 04 '14 at 05:07
  • Yea, I remembered noticing it in one of the examples when I was going through. Trying to set myself in the habit now :) – Logan Jun 04 '14 at 05:10
  • Check out the `find` global function. You can use it like this `var array = ["blue"]; var index = find(array, "blue");` – aleclarson Jun 08 '14 at 11:09
  • where would I put the array extension if I want to use it in multiple files? – Mike Jun 19 '14 at 22:58
  • 4
    For this to work under Swift 2 / XCode 7 you need to modify it as follows. Replace (includedElement: T -> Bool) with (includedElement: Element -> Bool) and change enumerate(self) to self.enumerate – Scooter Mar 07 '16 at 00:42
49

Swift 5

func firstIndex(of element: Element) -> Int?

var alphabets = ["A", "B", "E", "D"]

Example1

let index = alphabets.firstIndex(where: {$0 == "A"})

Example2

if let i = alphabets.firstIndex(of: "E") {
    alphabets[i] = "C" // i is the index
}
print(alphabets)
// Prints "["A", "B", "C", "D"]"
TheAlienMann
  • 151
  • 1
  • 11
Saranjith
  • 11,242
  • 5
  • 69
  • 122
  • The `where` version has this signature: `func firstIndex(where: (Element) -> Bool) -> Int?` And, since firstIndex has a trailing closure you could write for Example 1: `let index = alphabets.firstIndex { $0 == "A" }` :-) – John May 18 '22 at 01:00
32

While indexOf() works perfectly, it only returns one index.

I was looking for an elegant way to get an array of indexes for elements which satisfy some condition.

Here is how it can be done:

Swift 3:

let array = ["apple", "dog", "log"]

let indexes = array.enumerated().filter {
    $0.element.contains("og")
    }.map{$0.offset}

print(indexes)

Swift 2:

let array = ["apple", "dog", "log"]

let indexes = array.enumerate().filter {
    $0.element.containsString("og")
    }.map{$0.index}

print(indexes)
Serhii Yakovenko
  • 12,424
  • 2
  • 27
  • 26
  • 1
    Be careful when using this on subsequences, offset it is not the same as an index. Not all collections indices starts from zero. Note that this would only work with `Equatable` elements. `let indices = array.indices.filter { array[$0].contains("og") }` or using zip `let indices = zip(array.indices, array).filter { $1.contains("og") }.map(\.0)` – Leo Dabus Jul 17 '20 at 17:55
19

In Swift 4, the firstIndex method can be used. An example of using the == equality operator to find an object in an array by its id:

let index = array.firstIndex{ $0.id == object.id }
  • note this solution avoids your code needing to conform to the Equitable protocol as we're comparing the property and not the entire object

Also, a note about == vs === since many of the answers posted so far have differed in their usage:

  • == is the equality operator. It checks if values are equal.
  • === is the identity operator. It checks whether two instances of a class point to the same memory. This is different from equality, because two objects that were created independently using the same values will be considered equal using == but not === because they are different objects. (Source)

It would be worth it to read more on these operators from Swift's documentation.

Dwigt
  • 729
  • 6
  • 16
16

in Swift 4.2

.index(where:) was changed to .firstIndex(where:)

array.firstIndex(where: {$0 == "person1"})
Ridho Octanio
  • 543
  • 5
  • 14
15

For custom class, you need to implement the Equatable protocol.

import Foundation

func ==(l: MyClass, r: MyClass) -> Bool {
  return l.id == r.id
}

class MyClass: Equtable {
    init(id: String) {
        self.msgID = id
    }

    let msgID: String
}

let item = MyClass(3)
let itemList = [MyClass(1), MyClass(2), item]
let idx = itemList.indexOf(item)

printl(idx)
ZYiOS
  • 5,204
  • 3
  • 39
  • 45
13

Just use firstIndex method.

array.firstIndex(where: { $0 == searchedItem })
erdikanik
  • 674
  • 9
  • 11
10

Update for Swift 2:

sequence.contains(element): Returns true if a given sequence (such as an array) contains the specified element.

Swift 1:

If you're looking just to check if an element is contained inside an array, that is, just get a boolean indicator, use contains(sequence, element) instead of find(array, element):

contains(sequence, element): Returns true if a given sequence (such as an array) contains the specified element.

See example below:

var languages = ["Swift", "Objective-C"]
contains(languages, "Swift") == true
contains(languages, "Java") == false
contains([29, 85, 42, 96, 75], 42) == true
if (contains(languages, "Swift")) {
  // Use contains in these cases, instead of find.   
}
Bretsko
  • 608
  • 2
  • 7
  • 20
Zorayr
  • 23,770
  • 8
  • 136
  • 129
10

Swift 4. If your array contains elements of type [String: AnyObject]. So to find the index of element use the below code

var array = [[String: AnyObject]]()// Save your data in array
let objectAtZero = array[0] // get first object
let index = (self.array as NSArray).index(of: objectAtZero)

Or If you want to found index on the basis of key from Dictionary. Here array contains Objects of Model class and I am matching id property.

   let userId = 20
    if let index = array.index(where: { (dict) -> Bool in
           return dict.id == userId // Will found index of matched id
    }) {
    print("Index found")
    }
OR
      let storeId = Int(surveyCurrent.store_id) // Accessing model key value
      indexArrUpTo = self.arrEarnUpTo.index { Int($0.store_id) == storeId }! // Array contains models and finding specific one
Gurjinder Singh
  • 9,221
  • 1
  • 66
  • 58
7

For (>= swift 4.0)

It's rather very simple. Consider the following Array object.

var names: [String] = ["jack", "rose", "jill"]

In order to obtain the index of the element rose, all you have to do is:

names.index(of: "rose") // returns 1

Note:

  • Array.index(of:) returns an Optional<Int>.

  • nil implies that the element isn't present in the array.

  • You might want to force-unwrap the returned value or use an if-let to get around the optional.

mzaink
  • 261
  • 4
  • 10
6

In Swift 4, if you are traversing through your DataModel array, make sure your data model conforms to Equatable Protocol , implement the lhs=rhs method , and only then you can use ".index(of" . For example

class Photo : Equatable{
    var imageURL: URL?
    init(imageURL: URL){
        self.imageURL = imageURL
    }

    static func == (lhs: Photo, rhs: Photo) -> Bool{
        return lhs.imageURL == rhs.imageURL
    }
}

And then,

let index = self.photos.index(of: aPhoto)
Naishta
  • 11,885
  • 4
  • 72
  • 54
5

In Swift 2 (with Xcode 7), Array includes an indexOf method provided by the CollectionType protocol. (Actually, two indexOf methods—one that uses equality to match an argument, and another that uses a closure.)

Prior to Swift 2, there wasn't a way for generic types like collections to provide methods for the concrete types derived from them (like arrays). So, in Swift 1.x, "index of" is a global function... And it got renamed, too, so in Swift 1.x, that global function is called find.

It's also possible (but not necessary) to use the indexOfObject method from NSArray... or any of the other, more sophisticated search meth dis from Foundation that don't have equivalents in the Swift standard library. Just import Foundation (or another module that transitively imports Foundation), cast your Array to NSArray, and you can use the many search methods on NSArray.

Cœur
  • 37,241
  • 25
  • 195
  • 267
rickster
  • 124,678
  • 26
  • 272
  • 326
5

Swift 2.1

var array = ["0","1","2","3"]

if let index = array.indexOf("1") {
   array.removeAtIndex(index)
}

print(array) // ["0","2","3"]

Swift 3

var array = ["0","1","2","3"]

if let index = array.index(of: "1") {
    array.remove(at: index)
}
array.remove(at: 1)
Luca Davanzo
  • 21,000
  • 15
  • 120
  • 146
5

Any of this solution works for me

This the solution i have for Swift 4 :

let monday = Day(name: "M")
let tuesday = Day(name: "T")
let friday = Day(name: "F")

let days = [monday, tuesday, friday]

let index = days.index(where: { 
            //important to test with === to be sure it's the same object reference
            $0 === tuesday
        })
Kevin ABRIOUX
  • 16,507
  • 12
  • 93
  • 99
3

You can also use the functional library Dollar to do an indexOf on an array as such http://www.dollarswift.org/#indexof-indexof

$.indexOf([1, 2, 3, 1, 2, 3], value: 2) 
=> 1
Encore PTL
  • 8,084
  • 10
  • 43
  • 78
3

If you are still working in Swift 1.x

then try,

let testArray = ["A","B","C"]

let indexOfA = find(testArray, "A") 
let indexOfB = find(testArray, "B")
let indexOfC = find(testArray, "C")
iCyberPaul
  • 650
  • 4
  • 15
3

For SWIFT 3 you can use a simple function

func find(objecToFind: String?) -> Int? {
   for i in 0...arrayName.count {
      if arrayName[i] == objectToFind {
         return i
      }
   }
return nil
}

This will give the number position, so you can use like

arrayName.remove(at: (find(objecToFind))!)

Hope to be useful

Marco
  • 71
  • 2
3

In Swift 4/5, use "firstIndex" for find index.

let index = array.firstIndex{$0 == value}
Krunal Patel
  • 1,649
  • 1
  • 14
  • 22
2

Swift 4

For reference types:

extension Array where Array.Element: AnyObject {

    func index(ofElement element: Element) -> Int? {
        for (currentIndex, currentElement) in self.enumerated() {
            if currentElement === element {
                return currentIndex
            }
        }
        return nil
    }
}
Menno
  • 1,232
  • 12
  • 23
2

In case somebody has this problem

Cannot invoke initializer for type 'Int' with an argument list of type '(Array<Element>.Index?)'

jsut do this

extension Int {
    var toInt: Int {
        return self
    }
}

then

guard let finalIndex = index?.toInt else {
    return false
}
Ahmed Safadi
  • 4,402
  • 37
  • 33
1

SWIFT 4

Let's say you want to store a number from the array called cardButtons into cardNumber, you can do it this way:

let cardNumber = cardButtons.index(of: sender)

sender is the name of your button