Swift Array - Перевірте, чи існує індекс


140

Чи є у 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
}

Але я отримую

фатальна помилка: індекс масиву поза діапазоном

Відповіді:


442

Елегантний спосіб у Swift:

let isIndexValid = array.indices.contains(index)

10
У реальному житті це не має значення, але складність у часі, чи не було б кориснішою index < array.count?
funct7

2
Я чесно вважаю, що приклад, який ви подали, дещо надуманий, оскільки я ніколи не відчував негативного значення індексу, але якщо це дійсно так, два справжні / помилкові перевірки будуть все-таки кращими: тобто index >= 0 && index < array.countзамість того, щоб найгірший випадок був n порівняння.
funct7

13
Якщо вам цікаво, що таке різниця швидкостей, я вимірював її інструментами Xcode, і це мізерно. gist.github.com/masonmark/a79bfa1204c957043687f4bdaef0c2ad
Мейсон

7
Просто додамо ще одне питання про те, чому це правильно: якщо ви використовуєте ту саму логіку під час роботи над ArraySlice, перший індекс не буде 0, тому index >= 0перевірка не буде достатньо хорошою перевіркою. .indicesнатомість працює в будь-якому випадку.
DeFrenZ

2
Я люблю Свіфта за це. Ось мій випадок використання: побудова лайна структура JSON для обслуговування, тому довелося це робити: "listendee3": names.indices.contains (2)? імена [2]: ""
Лі Проберт

64

Розширення типу:

extension Collection {

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

}

Використовуючи це, ви отримуєте необов’язкове значення, додаючи ключове слово, необов’язкове до вашого індексу, що означає, що ваша програма не виходить з ладу, навіть якщо індекс знаходиться поза діапазоном. У вашому прикладі:

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в параметрі. Дякую!
Якуб

2
Дивовижність: D врятував мені день.
Codetard

2
Це прекрасно
JoeGalind

32

Просто перевірте, чи не перевищує показник розмір масиву:

if 2 < arr.count {
    ...
} else {
    ...
}

3
Що робити, якщо розмір масиву невідомий?
Натан Маккаскле

8
@NathanMcKaskle Масив завжди знає, скільки елементів він містить, тому розмір не може бути невідомим
Антоніо

16
Вибачте, що там не думав. Ранкова кава все ще надходила в кров.
Натан Маккаскле

4
@NathanMcKaskle не хвилюйся ... іноді це трапляється зі мною навіть після декількох кав ;-)
Антоніо

У чомусь це найкраща відповідь, оскільки він працює в O (1) замість O (n).
ScottyBlades

12

Додайте трохи цукру-подовжувача:

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
чесно кажучи, це найшвидший спосіб зробити це з максимальною
читальністю

1
@barndog Любіть це теж. Я зазвичай додаю це як цукор у будь-який проект, який я починаю. Доданий також приклад охорони. Подяка швидкій слабкій громаді за те, що вона придумала цю.
еоніст

гарне рішення ... але вихід буде надрукувати "d", наприклад, якщо ви даєте ...
jayant rawat

Це має бути частиною мови Swift. Випуск індексу поза діапазону не менш проблематичний, ніж нульові значення. Я б навіть запропонував використовувати подібний синтаксис: можливо, щось на кшталт: myArray [? 3]. Якщо myArray необов’язковий, ви отримаєте myArray? [? 3]
Енді Вайнштейн

@Andy Weinstein Вам слід запропонувати це яблуко 💪
еоніст

7

Ви можете переписати це більш безпечним способом, щоб перевірити розмір масиву та використовувати термінальний умовний:

if let str2 = (arr.count > 2 ? arr[2] : nil) as String?

1
Те, що пропонує Антоніо, є набагато прозорішим (та ефективнішим). У випадку, коли потрійне було б доречним, це якби у вас було цілком доступне значення для використання та усунення, якщо взагалі, просто залишаючи заяву про дозвіл.
Девід Беррі

1
@David Що пропонує Антоніо, потрібні два ifтвердження замість одного ifтвердження в оригінальному коді. Мій код замінює другий ifумовним оператором, дозволяючи вам зберігати єдиний, elseа не примушувати два окремих elseблоки.
dasblinkenlight

1
Я не бачу двох, якщо заяви у його коді, поставте str2 = arr [1] для його еліпсису і це зроблено. Якщо ви заміните OP, якщо дозволите оператору antonio, якщо оператор if, і перемістіть завдання всередину (або ні, оскільки єдине використання str2 - це друкувати його взагалі немає необхідності, просто покладіть вказівку dereference в println. Не кажучи вже про те що потрійний оператор - просто неясний (для деяких :)) if / else. Якщо обр. count> 2, тоді arr [2] ніколи не може бути нульовим, тому навіщо його відображати у String? просто так, ви можете застосувати ще один if твердження.
Девід Беррі

@David Цілий ifзапитання ОП закінчиться всередині "тоді" гілки відповіді Антоніо, тож було б два вкладені ifs. Я розглядаю код ОП як невеликий приклад, тому я припускаю, що він все-таки захотів би його if. Я згоден з вами, що в його прикладі ifне потрібно. Але знову ж таки, весь вислів є безглуздим, оскільки ОП знає, що масив не має достатньої довжини, і що жоден з його елементів не є nil, тому він міг видалити ifта зберегти лише його elseблок.
dasblinkenlight

Ні, не буде. Антоніо, якщо замінює заяву ОП, якщо заява. Оскільки тип масиву є [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


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
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 .


Що робити, якщо індекс негативний? Ви, ймовірно, повинні також це перевірити.
Френкі Сімон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.