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.