Swift Array - проверьте, существует ли индекс

148

В Swift есть ли способ проверить, существует ли индекс в массиве без фатальной ошибки?

Я надеялся, что смогу сделать что-то вроде этого:

let arr: [String] = ["foo", "bar"]
let str: String? = arr[1]
if let str2 = arr[2] as String? {
    // this wouldn't run
    println(str2)
} else {
    // this would be run
}

Но я получаю

фатальная ошибка: индекс массива вне допустимого диапазона

chrishale
источник

Ответы:

470

Элегантный способ в Swift:

let isIndexValid = array.indices.contains(index)
Мануэль
источник
11
В реальной жизни это не должно иметь значения, но с точки зрения временной сложности не лучше ли использовать index < array.count?
funct7 03
13
Если вам интересно, какая разница в скорости, я измерил ее с помощью инструментов Xcode, и она незначительна. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Мейсон
7
Просто чтобы добавить еще один момент, почему это правильно: если вы примените ту же логику при работе с an ArraySlice, первый индекс не будет равен 0, поэтому index >= 0проверка не будет достаточно хорошей. .indicesвместо этого работает в любом случае.
DeFrenZ
2
Я люблю Swift за это. Вот мой пример использования: создание дерьмовой структуры JSON для службы, поэтому пришлось сделать это: "members3": names.indices.contains (2)? names [2]: ""
Ли Проберт
1
Это хороший ответ. Чтобы ответить на озабоченность @ funct7 временной сложностью, тип, возвращаемый методом index для Array, является диапазоном, а не массивом целых чисел. Таким образом, временная сложность составляет O (1), как и при ручной проверке верхней и нижней границы.
Крис Лорел
68

Расширение типа:

extension Collection {

    subscript(optional i: Index) -> Iterator.Element? {
        return self.indices.contains(i) ? self[i] : nil
    }

}

Используя это, вы получите необязательное значение при добавлении ключевого слова optional к вашему индексу, что означает, что ваша программа не выйдет из строя, даже если индекс находится вне диапазона. В вашем примере:

let arr = ["foo", "bar"]
let str1 = arr[optional: 1] // --> str1 is now Optional("bar")
if let str2 = arr[optional: 2] {
    print(str2) // --> this still wouldn't run
} else {
    print("No string found at that index") // --> this would be printed
}
Бенно Кресс
источник
4
Отличный ответ 👏 Самое главное, что он читается при использовании optionalпараметра in. Благодарность!
Якуб
2
Великолепие: D Спасла мне день.
Codetard
2
Это красиво
JoeGalind
31

Просто проверьте, меньше ли индекс размера массива:

if 2 < arr.count {
    ...
} else {
    ...
}
Антонио
источник
3
Что делать, если размер массива неизвестен?
Натан МакКаскл
9
@NathanMcKaskle Массив всегда знает, сколько элементов он содержит, поэтому размер не может быть неизвестен
Антонио
17
Извини, что не думал об этом. Утренний кофе все еще поступал в кровоток.
Натан МакКаскл
4
@NathanMcKaskle, не беспокойтесь ... иногда такое случается со мной, даже после нескольких чашек кофе ;-)
Антонио
В некотором смысле это лучший ответ, потому что он выполняется в O (1) вместо O (n).
ScottyBlades
13

Добавьте немного сахара-удлинителя:

extension Collection {
  subscript(safe index: Index) -> Iterator.Element? {
    guard indices.contains(index) else { return nil }
    return self[index]
  }
}

if let item = ["a", "b", "c", "d"][safe: 3] { print(item) }//Output: "d"
//or with guard:
guard let anotherItem = ["a", "b", "c", "d"][safe: 3] else {return}
print(anotherItem) // "d"

Повышает удобочитаемость при if letкодировании стиля в сочетании с массивами

эонист
источник
2
честно говоря, это самый быстрый способ сделать это с максимальной читабельностью и ясностью
barndog
1
@barndog Мне тоже это нравится. Я обычно добавляю это как сахар в любой проект, который я запускаю. Также добавлен пример охранника. Благодарим сообщество Swift Slack за его создание.
eonist
хорошее решение ... но в выводе будет напечатано "d" в примере, который вы даете ...
jayant rawat
Это должно быть частью языка Swift. Вопрос выхода индекса за пределы диапазона не менее проблематичен, чем нулевые значения. Я бы даже предложил использовать аналогичный синтаксис: возможно, что-то вроде: myArray [? 3]. Если myArray является необязательным, вы получите myArray? [? 3]
Энди Вайнштейн
@ Энди Вайнштейн. Вы должны предложить это Apple 💪
eonist
7

Вы можете переписать это более безопасным способом, чтобы проверить размер массива, и использовать тернарное условное выражение:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?
Сергей Калиниченко
источник
1
То, что предлагает Антонио, гораздо более прозрачно (и эффективно). Тернар был бы уместен, если бы у вас было легко доступное значение для использования и полностью исключите if, просто оставив оператор let.
Дэвид Берри
1
@David То, что предлагает Антонио, требует двух ifоператоров вместо одного ifв исходном коде. В моем коде второй заменяется ifусловным оператором, что позволяет сохранить один elseвместо принудительного использования двух отдельных elseблоков.
Сергей Калиниченко
1
Я не вижу двух операторов if в его коде, вставьте let str2 = arr [1] вместо его многоточия, и готово. Если вы замените оператор OP if let на оператор antonio if и переместите присвоение внутрь (или нет, поскольку единственное использование str2 - это его печать, в этом нет необходимости, просто поместите разыменование встроенным в println. Не говоря уже о что тернарный оператор - это просто непонятное (для некоторых :)) if / else. Если обр. count> 2, тогда arr [2] никогда не может быть nil, так зачем отображать его в String? просто чтобы вы могли применить еще один оператор if.
Дэвид Берри
@David Весь вопрос ifиз OP окажется внутри ветки «then» ответа Антонио, поэтому будет два вложенных ifs. Я рассматриваю код OP в качестве небольшого примера, поэтому предполагаю, что ему все еще нужен файл if. Я согласен с вами, что в его примере ifэто не нужно. Но опять же, весь оператор бессмысленен, потому что OP знает, что у массива недостаточно длины и что нет ни одного из его элементов nil, поэтому он может удалить ifи оставить только его elseблок.
Сергей Калиниченко
Нет, не будет. Если Антонио заменяет оператор if в OP. Поскольку тип массива - [String], вы знаете, что он никогда не может содержать ноль, поэтому не нужно проверять что-либо, кроме длины.
Дэвид Берри
7

Расширение Swift 4:

Для меня я предпочитаю подобный метод.

// MARK: - Extension Collection

extension Collection {

    /// Get at index object
    ///
    /// - Parameter index: Index of object
    /// - Returns: Element at index or nil
    func get(at index: Index) -> Iterator.Element? {
        return self.indices.contains(index) ? self[index] : nil
    }
}

Спасибо @Benno Kress

ЯннСтеф
источник
2
extension Array {
    func isValidIndex(_ index : Int) -> Bool {
        return index < self.count
    }
}

let array = ["a","b","c","d"]

func testArrayIndex(_ index : Int) {

    guard array.isValidIndex(index) else {
        print("Handle array index Out of bounds here")
        return
    }

}

Мне нужно обрабатывать indexOutOfBounds .

Pratik Sodha
источник
Что делать, если индекс отрицательный? Возможно, вам стоит проверить и это.
Фрэнки Саймон,
1

Утверждение, существует ли индекс массива:

Эта методика отлично подходит, если вы не хотите добавлять сахар для расширения:

let arr = [1,2,3]
if let fourthItem = (3 < arr.count ?  arr[3] : nil ) {
     Swift.print("fourthItem:  \(fourthItem)")
}else if let thirdItem = (2 < arr.count ?  arr[2] : nil) {
     Swift.print("thirdItem:  \(thirdItem)")
}
//Output: thirdItem: 3
эонист
источник
1

Расширение Swift 4 и 5:

На мой взгляд, это самое безопасное решение:

public extension MutableCollection {
    subscript(safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set(newValue) {
            if let newValue = newValue, indices.contains(index) {
                self[index] = newValue
            }
        }
    }
}

Пример:

let array = ["foo", "bar"]
if let str = array[safe: 1] {
    print(str) // "bar"
} else {
    print("index out of range")
}
RunesReader
источник