Swift: второе вхождение с indexOf
let numbers = [1,3,4,5,5,9,0,1]
Найти первый 5
, используйте:
numbers.indexOf(5)
Как мне найти второй случай?
6 ответов
Вы можете выполнить другой поиск индекса элемента в оставшейся части массива следующим образом:
редактировать / обновлять: Xcode 9 • Swift 4
extension Collection where Element: Equatable {
func secondIndex(of element: Element) -> Index? {
guard let index = index(of: element) else { return nil }
return self[self.index(after: index)...].index(of: element)
}
func indexes(of element: Element) -> [Index] {
var indexes: [Index] = []
var position = self.startIndex
while let index = self[position...].index(of: element) {
indexes.append(index)
position = self.index(after: index)
}
return indexes
}
}
Тестирование:
let numbers = [1,3,4,5,5,9,0,1]
numbers.secondIndex(of: 5) // 4
numbers.indexes(of: 5) // [3,4]
Как только вы нашли первое вхождение, вы можете использовать indexOf
на оставшейся части массива, чтобы найти второе вхождение:
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
}
Вот расширение общего пользования Array
это будет работать для поиска n-го элемента в любом массиве:
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
Я не думаю, что вы можете сделать это с indexOf
, Вместо этого вам придется использовать for-loop
, Сокращенная версия:
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]
РЕДАКТИРОВАТЬ: согласно комментарию @davecom, я включил аналогичное, но немного более сложное решение в нижней части ответа.
Здесь я вижу пару хороших решений, особенно учитывая ограничения относительно нового языка Swift. Существует и очень лаконичный способ сделать это, но будьте осторожны... это довольно быстро и грязно. Возможно, не идеальное решение, но оно довольно быстрое. Также очень универсален (не хвастаться).
extension Array where Element: Equatable {
func indexes(search: Element) -> [Int] {
return enumerate().reduce([Int]()) { $1.1 == search ? $0 + [$1.0] : $0 }
}
}
Используя это расширение, вы можете получить доступ ко второму индексу следующим образом:
let numbers = [1, 3, 4, 5, 5, 9, 0, 1]
let indexesOf5 = numbers.indexes(5) // [3, 4]
indexesOf5[1] // 4
И вы сделали!
В основном, метод работает так: enumerate()
сопоставляет массив с кортежами, включая индекс каждого элемента с самим элементом. В этом случае, [1, 3, 4, 5, 5, 9, 0, 1].enumerate()
возвращает коллекцию типа EnumerateSequence<Array<Int>>
который, переведенный в массив In teger, возвращает [(0,1), (1,3), (2,4), (3,5), (4,5), (5,9), (6,0), (7,1)]
,
Остальная часть работы выполнена с использованием reduce
(в некоторых языках называется "впрыскивать"), что является чрезвычайно мощным инструментом, с которым многие кодеры не знакомы. Если читатель входит в число этих программистов, я бы порекомендовал ознакомиться с этой статьей, касающейся использования функции в JS (имейте в виду, что расположение передаваемого неблокированного аргумента вводится после блока в JS, а не до того, как видно Вот).
Спасибо за прочтение.
PS не слишком затянуто относительно этого относительно простого решения, но если синтаксис для indexes
показанный выше метод слишком быстр и грязен, вы можете попробовать что-то вроде этого в теле метода, где параметры замыкания раскрыты для большей ясности:
return enumerate().reduce([Int]()) { memo, element in
element.1 == search ? memo + [element.0] : memo
}
РЕДАКТИРОВАТЬ: Вот еще одна опция, которая позволяет разработчику сканировать определенный "индекс по индексу" (например, второе вхождение 5) для более эффективного решения.
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
Новый метод все еще выполняет итерации по всему массиву, но он намного более эффективен из-за отсутствия "построения массива" в предыдущем методе. Это снижение производительности будет незначительным при использовании массива из 8 объектов, используемого для большинства. Но рассмотрим список из 10000 случайных чисел от 0 до 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
Как видно, этот новый метод значительно быстрее с (очень) большим набором данных; хотя это немного громоздко и запутанно, поэтому, в зависимости от вашего приложения, вы можете предпочесть более простое решение или совсем другое.
(Еще раз) спасибо за чтение.
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
}
}