let numbers = [1,3,4,5,5,9,0,1]
To find the first 5
, use:
numbers.indexOf(5)
How do I find the secon
Here's a general use extension of Array
that will work for finding the nth element of a kind in any array:
extension Array where Element: Equatable {
// returns nil if there is no nth occurence
// or the index of the nth occurence if there is
func findNthIndexOf(n: Int, thing: Element) -> Int? {
guard n > 0 else { return nil }
var count = 0
for (index, item) in enumerate() where item == thing {
count += 1
if count == n {
return index
}
}
return nil
}
}
let numbers = [1,3,4,5,5,9,0]
numbers.findNthIndexOf(2, thing: 5) // returns 4
Once you've found the first occurrence, you can use indexOf
on the remaining slice of the array to locate the second occurrence:
let numbers = [1,3,4,5,5,9,0,1]
if let firstFive = numbers.indexOf(5) { // 3
let secondFive = numbers[firstFive+1..<numbers.count].indexOf(5) // 4
}
EDIT: as per @davecom's comment, I've included a similar but slightly more complex solution at the bottom of the answer.
I see a couple of good solutions here, especially considering the limitations the relatively new language of Swift. There is a really concise way to do it too, but beware...it is rather quick-and-dirty. May not be the perfect solution, but it is pretty quick. Also very versatile (not to brag).
extension Array where Element: Equatable {
func indexes(search: Element) -> [Int] {
return enumerate().reduce([Int]()) { $1.1 == search ? $0 + [$1.0] : $0 }
}
}
Using this extension, you could access the second index as follows:
let numbers = [1, 3, 4, 5, 5, 9, 0, 1]
let indexesOf5 = numbers.indexes(5) // [3, 4]
indexesOf5[1] // 4
And you're done!
Basically, the method works like this: enumerate()
maps the array to tuples including the index of each element with the element itself. In this case, [1, 3, 4, 5, 5, 9, 0, 1].enumerate()
returns a collection of the type EnumerateSequence<Array<Int>>
which, translated to an Integer array, returns [(0,1), (1,3), (2,4), (3,5), (4,5), (5,9), (6,0), (7,1)]
.
The rest of the work is done using reduce
(called 'inject' in some languages), which is an extremely powerful tool that many coders are not familiar with. If the reader is among those coders, I'd recommend checking out this article regarding use of the function in JS (keep in mind the placement of the non-block argument passed in is inputted after the block in JS, rather than before as seen here).
Thanks for reading.
P.S. not to be too long-winded on this relatively simple solution, but if the syntax for the indexes
method shown above is a bit too quick-and-dirty, you could try something like this in the method body, where the closure's parameters are expanded for a bit more clarity:
return enumerate().reduce([Int]()) { memo, element in
element.1 == search ? memo + [element.0] : memo
}
EDIT: Here's another option that allows the implementer to scan for a specific "index at index" (e.g. the second occurrence of 5) for a more efficient solution.
extension Array where Element: Equatable {
func nIndex(search: Element, n: Int) -> Int? {
let info = enumerate().reduce((count: 0, index: 0), combine: { memo, element in
memo.count < n && element.1 == search ? (count: memo.count + 1, index: element.0) : memo
})
return info.count == n ? info.index : nil
}
}
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 2) // 4
[1, 3, 4, 5, 5, 9, 0, 1].nIndex(5, n: 3) // nil
The new method still iterates over the entire array, but is much more efficient due to the lack of "array-building" in the previous method. That performance hit would be negligible with the 8-object array used for the majority. But consider a list of 10,000 random numbers from 0 to 99:
let randomNumbers = (1...10000).map{_ in Int(rand() % 100)}
let indexes = randomNumbers.indexes(93) // count -> 100 (in my first run)
let index1 = indexes[1] // 238
// executed in 29.6603130102158 sec
let index2 = randomNumbers.nIndex(93, n: 2) // 238
// executed in 3.82625496387482 sec
As can be seen, this new method is considerably faster with the (very) large dataset; it is a bit more cumbersome and confusing though, so depending on your application, you may prefer the simpler solution, or a different one entirely.
(Again) thanks for reading.
I don't think you can do it with indexOf
. Instead you'll have to use a for-loop
. A shorthand version:
let numbers = [1,3,4,5,5,9,0,1]
var indexes = [Int]()
numbers.enumerate().forEach { if $0.element == 5 { indexes += [$0.index] } }
print(indexes) // [3, 4]
extension Collection where Element: Equatable {
func nth(occurance: Int, of element: Element) -> Index? {
var level : Int = occurance
var position = self.startIndex
while let index = self[position...].index(of: element) {
level -= 1
guard level >= 0 else { return nil }
guard level != 0 else { return index }
position = self.index(after: index)
}
return nil
}
}
You can perform another search for the index of element at the remaining array slice as follow:
edit/update: Swift 5.2 or later
extension Collection where Element: Equatable {
/// Returns the second index where the specified value appears in the collection.
func secondIndex(of element: Element) -> Index? {
guard let index = firstIndex(of: element) else { return nil }
return self[self.index(after: index)...].firstIndex(of: element)
}
}
extension Collection {
/// Returns the second index in which an element of the collection satisfies the given predicate.
func secondIndex(where predicate: (Element) throws -> Bool) rethrows -> Index? {
guard let index = try firstIndex(where: predicate) else { return nil }
return try self[self.index(after: index)...].firstIndex(where: predicate)
}
}
Testing:
let numbers = [1,3,4,5,5,9,0,1]
if let index = numbers.secondIndex(of: 5) {
print(index) // "4\n"
} else {
print("not found")
}
if let index = numbers.secondIndex(where: { $0.isMultiple(of: 3) }) {
print(index) // "5\n"
} else {
print("not found")
}