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
    }
}
Другие вопросы по тегам