İsteğe bağlı bağlamalar aracılığıyla Swift'te güvenli (sınır işaretli) dizi arama?


271

Swift bir dizi varsa ve sınırları dışında bir dizine erişmeye çalışırsanız, şaşırtıcı bir çalışma zamanı hatası var:

var str = ["Apple", "Banana", "Coconut"]

str[0] // "Apple"
str[3] // EXC_BAD_INSTRUCTION

Ancak, Swift'in getirdiği tüm isteğe bağlı zincirleme ve güvenlikle düşünürdüm, şöyle bir şey yapmak önemsiz olurdu:

let theIndex = 3
if let nonexistent = str[theIndex] { // Bounds check + Lookup
    print(nonexistent)
    ...do other things with nonexistent...
}

Onun yerine:

let theIndex = 3
if (theIndex < str.count) {         // Bounds check
    let nonexistent = str[theIndex] // Lookup
    print(nonexistent)   
    ...do other things with nonexistent... 
}

Ama durum böyle değil - ben end ififadesinin daha az olduğundan emin olmak ve kontrol etmek için ol ifadesini kullanmalıyım str.count.

Kendi subscript()uygulamamı eklemeyi denedim , ancak çağrıyı orijinal uygulamaya geçirmek veya alt simge gösterimi kullanmadan öğelere (dizin tabanlı) erişmek için nasıl emin değilim:

extension Array {
    subscript(var index: Int) -> AnyObject? {
        if index >= self.count {
            NSLog("Womp!")
            return nil
        }
        return ... // What?
    }
}

2
Bunun biraz OT olduğunu anlıyorum, ancak Swift'in listeler de dahil olmak üzere herhangi bir sınır kontrolü yapmak için açık sözdizimine sahip olmanın iyi olacağını hissediyorum. Bunun için zaten uygun bir anahtar kelimemiz var. Örneğin, (1,2,7) 'de X ise veya myArray'de X varsa
Maury Markowitz

Yanıtlar:


652

Alex'in cevabı soru için iyi bir tavsiye ve çözüme sahip, ancak bu işlevselliği uygulamanın daha güzel bir yolunda rastladım:

Swift 3.2 ve daha yenisi

extension Collection {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3.0 ve 3.1

extension Collection where Indices.Iterator.Element == Index {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Swift 3 için çözüm bulduğu için Hamish'e teşekkür ederiz .

Hızlı 2

extension CollectionType {

    /// Returns the element at the specified index if it is within bounds, otherwise nil.
    subscript (safe index: Index) -> Generator.Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

Misal

let array = [1, 2, 3]

for index in -20...20 {
    if let item = array[safe: index] {
        print(item)
    }
}

45
Bence bu kesinlikle ilgiyi hak ediyor - iyi iş. safe:Farkı sağlamak için dahil edilen parametre adını seviyorum .
Craig Otis

11
Swift 2'den (Xcode 7) itibaren bu küçük bir değişiklik gerekiyor:return self.indices ~= index ? self[index] : nil;
Tim

7
Swift 3 versiyonunu ilgili olarak: muhtemelen bir köşe harf salt istemi, ancak bir istem yine: Yukarıdaki "güvenli" alt simge sürümü (Swift 2 versiyonu iken) güvenli değildir durumlar vardır: için Collectiontürleri Indicesvardır bitişik değil. Örneğin Set, bir set öğesine index ( SetIndex<Element>) ile erişecek olsaydık, endeksler için çalışma zamanı istisnaları ile çalışabiliriz >= startIndexve < endIndexbu durumda güvenli alt simge başarısız olur (örneğin, bu yapışık örneğe bakınız ).
dfri

12
UYARI! Dizileri bu şekilde kontrol etmek gerçekten pahalı olabilir. containsDolayısıyla bu yöntem, bu bir O (n) oluşturan bütün indekslerle yineleme olacaktır. Daha iyi bir yol, sınırları kontrol etmek için endeksi ve sayımı kullanmaktır.
Stefan Vasiljevic

6
Endeksleri üretilmesi ve bunların yineleme önlemek için (O (n)), daha iyi kullanım karşılaştırmaları için (burada O (1)): return index >= startIndex && index < endIndex ? self[index] : nil Collectiontipleri, startIndex, endIndexvardır Comparable. Tabii ki, bu, örneğin ortada endeksleri olmayan, çözümün indicesdaha genel olan bazı garip koleksiyonlar için işe yaramaz .
zubko

57

Bu davranışı gerçekten istiyorsanız, Dizi yerine bir Sözlük istediğiniz gibi kokar. Sözlükler dönmek nilerişen bu anahtarlar nerede dizideki anahtar, her şey olabilir çünkü anahtar bir sözlükte mevcut olup olmadığını bilmek çok zor çünkü hangi mantıklı anahtarları eksik olduğunda mutlaka : bir dizi 0için count. Ve bir döngüdeki her yinelemede gerçek bir değere sahip olduğunuzdan kesinlikle emin olabileceğiniz bu aralık üzerinde tekrarlamak inanılmaz derecede yaygındır .

Bu şekilde çalışmamasının sebebinin Swift geliştiricileri tarafından yapılan bir tasarım seçimi olduğunu düşünüyorum. Örneğinizi alın:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0] )"

Dizinin zaten mevcut olduğunu biliyorsanız, çoğu durumda bir dizi kullandığınız gibi, bu kod harikadır. Bir simge erişen muhtemelen geri dönebilirler Ancak, nilo zaman dönüş türü değişmiş ait Arraybireyin subscriptisteğe bağlı olarak yöntemin. Bu, kodunuzu şu şekilde değiştirir:

var fruits: [String] = ["Apple", "Banana", "Coconut"]
var str: String = "I ate a \( fruits[0]! )"
//                                     ^ Added

Bu, bir dizide her yineleme yaptığınızda veya bilinen bir dizinde başka bir şey yaptığınızda isteğe bağlı bir paketin açılması gerektiğinden, nadiren sınırların dışına çıkma dizinine erişebildiğiniz anlamına gelir. Swift tasarımcıları, sınırların dışındaki dizinlere erişirken çalışma zamanı istisnası pahasına, seçeneklerin daha az paketlenmesini seçti. Ve bir çökme, nilverilerinizde bir yerde beklemediğiniz bir mantık hatasına neden olur .

Onlara katılıyorum. Bu nedenle, varsayılan Arrayuygulamayı değiştirmeyeceksiniz çünkü dizilerden isteğe bağlı olmayan bir değer bekleyen tüm kodu kıracaksınız.

Bunun yerine, alt sınıfı sınıflandırabilir Arrayve subscriptisteğe bağlı olarak dönmek için geçersiz kılabilirsiniz . Veya daha pratik olarak, bunu Arrayyapan bir alt simge olmayan yöntemle genişletebilirsiniz .

extension Array {

    // Safely lookup an index that might be out of bounds,
    // returning nil if it does not exist
    func get(index: Int) -> T? {
        if 0 <= index && index < count {
            return self[index]
        } else {
            return nil
        }
    }
}

var fruits: [String] = ["Apple", "Banana", "Coconut"]
if let fruit = fruits.get(1) {
    print("I ate a \( fruit )")
    // I ate a Banana
}

if let fruit = fruits.get(3) {
    print("I ate a \( fruit )")
    // never runs, get returned nil
}

Swift 3 Güncellemesi

func get(index: Int) ->T? ile değiştirilmesi gerekiyor func get(index: Int) ->Element?


2
Dönüş türünü subscript()isteğe bağlı olarak değiştirmekten bahsettiğiniz için +1 (ve kabul) - bu, varsayılan davranışı geçersiz kılmada karşılaşılan birincil birlikte gösterimdi. (Aslında hiç işe yaramadı . ) get()Diğer senaryolarda (Obj-C kategorileri, kimse?) Bariz bir seçim olan bir uzantı yöntemi yazmaktan kaçınıyordum ama get(daha büyük değil [ve yapar davranışın diğer geliştiricilerin Swift alt simge işlecinden bekleyebileceklerinden farklı olabileceğini unutmayın. Teşekkür ederim!
Craig Otis

3
Daha da kısaltmak için ();)
hyouuu

7
Swift 2.0 Titibariyle yeniden adlandırıldı Element. Sadece dostça bir hatırlatma :)
Stas Zhukovskiy

3
Bu tartışmaya eklemek için, sınır denetiminin isteğe bağlı olarak dönmek için Swift'e dönüştürülmemesinin bir başka nedeni, sınırların nildışında bir dizinden istisna oluşturmak yerine geri dönmenin belirsiz olmasıdır. Örneğin Array<String?>, nil'i koleksiyonun geçerli bir üyesi olarak da döndürebileceğinden, bu iki durum arasında ayrım yapamazsınız. Asla bir nildeğer döndüremeyeceğinizi bildiğiniz kendi koleksiyon türünüz varsa , diğer bir deyişle uygulamaya bağlamsal olarak bağlıysa, bu yazıda yanıtlandığı gibi güvenli sınır kontrolü için Swift'i genişletebilirsiniz.
Aaron

Güzel çalışıyor
kamyFC

20

Nikita Kukushkin'in cevabı üzerine inşa etmek için bazen dizi dizinlerine güvenli bir şekilde atamanız ve onlardan okumanız gerekir, yani

myArray[safe: badIndex] = newValue

İşte Nikita'nın cevabında (Swift 3.2), safe: parametre adını ekleyerek değiştirilebilir dizi dizinlerine güvenli bir şekilde yazmayı sağlayan bir güncelleme.

extension Collection {
    /// Returns the element at the specified index iff it is within bounds, otherwise nil.
    subscript(safe index: Index) -> Element? {
        return indices.contains(index) ? self[ index] : nil
    }
}

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

2
Son derece az cevap! Bunu yapmanın doğru yolu budur!
Reid

14

Swift 2'de geçerlidir

Bu zaten birçok kez yanıtlanmış olsa da, Swift'in modasının nereye gittiğine dair daha fazla cevap sunmak istiyorum, ki bu Crusty'nin sözleriyle¹: " protocolÖnce düşün "

• Ne yapmak istiyoruz?
- Bir an Eleman alın Arraygüvenlidir ve yalnızca bir Endeksi verilen nilaksi
• ne yapmalıdır bu işlevsellik tabanı üzerinde 's uygulama?
- Array subscripting
• Bu özelliği nereden alıyor?
- Onun tanımı struct Arrayiçinde Swiftmodül o vardır
• daha genel / soyut bir şey?
- Hangisini de sağladığını benimserprotocol CollectionType
• Daha genel / soyut bir şey yok mu?
- O da benimser protocol Indexable...
• Evet, yapabileceğimiz en iyi gibi geliyor. Daha sonra istediğimiz bu özelliğe sahip olacak şekilde genişletebilir miyiz?
- Ama çok sınırlı tiplerimiz (hayır Int) ve özelliklerimiz var (hayırcount) ile çalışmak için!
• Bu yeterli olacak. Swift'in stdlib oldukça iyi yapılır;)

extension Indexable {
    public subscript(safe safeIndex: Index) -> _Element? {
        return safeIndex.distanceTo(endIndex) > 0 ? self[safeIndex] : nil
    }
}

¹: doğru değil, ama fikir veriyor


2
Hızlı bir acemi olarak bu cevabı anlamıyorum. Sondaki kod neyi temsil ediyor? Bu bir çözüm mü, eğer öyleyse, bunu nasıl kullanırım?
Thomas Tempelmann

3
Maalesef, bu cevap Swift 3 için artık geçerli değil, ancak süreç kesinlikle geçerli. Tek fark, şimdi Collectionmuhtemelen
durmalısın

11
extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • O (1) performans
  • kasa tipi
  • [MyType?] (her iki düzeyde de açılmamış olabilecek MyType ??) için Optionals ile doğru bir şekilde ilgilenir
  • Setler için sorun yaratmaz
  • özlü kod

İşte sizin için yaptığım bazı testler:

let itms: [Int?] = [0, nil]
let a = itms[safe: 0] // 0 : Int??
a ?? 5 // 0 : Int?
let b = itms[safe: 1] // nil : Int??
b ?? 5 // nil : Int?
let c = itms[safe: 2] // nil : Int??
c ?? 5 // 5 : Int?

10
  • Diziler sıfır değerleri depolayabileceğinden, bir dizi [index] çağrısı sınırların dışındaysa bir sıfır döndürmek mantıklı değildir.
  • Bir kullanıcının sınır sorunlarını nasıl ele almak istediğini bilmediğimizden, özel işleçleri kullanmak mantıklı değildir.
  • Bunun aksine, nesneleri açmak için geleneksel kontrol akışını kullanın ve tip güvenliğini sağlayın.

if let index = array.checkIndexForSafety (index: Int)

  let item = array[safeIndex: index] 

if let index = array.checkIndexForSafety (index: Int)

  array[safeIndex: safeIndex] = myObject
extension Array {

    @warn_unused_result public func checkIndexForSafety(index: Int) -> SafeIndex? {

        if indices.contains(index) {

            // wrap index number in object, so can ensure type safety
            return SafeIndex(indexNumber: index)

        } else {
            return nil
        }
    }

    subscript(index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

    // second version of same subscript, but with different method signature, allowing user to highlight using safe index
    subscript(safeIndex index:SafeIndex) -> Element {

        get {
            return self[index.indexNumber]
        }

        set {
            self[index.indexNumber] = newValue
        }
    }

}

public class SafeIndex {

    var indexNumber:Int

    init(indexNumber:Int){
        self.indexNumber = indexNumber
    }
}

1
İlginç bir yaklaşım. Herhangi bir sebep SafeIndexbir sınıf değil, bir sınıf mıdır?
stef

8

Hızlı 4

Daha geleneksel bir sözdizimini tercih edenler için bir uzantı:

extension Array {

    func item(at index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}

dizinlerin dizinini içerip içermediğini kontrol etmek için dizi öğelerini eşitlenebilir şekilde sınırlamanız gerekmez.
Leo Dabus

evet - iyi bir noktaya - sadece vs. DeleteObject, gibi ek güvenli yöntemleri için ihtiyaç olacağını
Matjan

5

Güvenli dizi olsun buldum, ayarla, ekle, kaldır çok yararlı. Diğerleri yakında yönetilmesi zorlaştığından hataları günlüğe kaydetmeyi ve yoksaymayı tercih ederim. Tam kod feryat

/**
 Safe array get, set, insert and delete.
 All action that would cause an error are ignored.
 */
extension Array {

    /**
     Removes element at index.
     Action that would cause an error are ignored.
     */
    mutating func remove(safeAt index: Index) {
        guard index >= 0 && index < count else {
            print("Index out of bounds while deleting item at index \(index) in \(self). This action is ignored.")
            return
        }

        remove(at: index)
    }

    /**
     Inserts element at index.
     Action that would cause an error are ignored.
     */
    mutating func insert(_ element: Element, safeAt index: Index) {
        guard index >= 0 && index <= count else {
            print("Index out of bounds while inserting item at index \(index) in \(self). This action is ignored")
            return
        }

        insert(element, at: index)
    }

    /**
     Safe get set subscript.
     Action that would cause an error are ignored.
     */
    subscript (safe index: Index) -> Element? {
        get {
            return indices.contains(index) ? self[index] : nil
        }
        set {
            remove(safeAt: index)

            if let element = newValue {
                insert(element, safeAt: index)
            }
        }
    }
}

Testler

import XCTest

class SafeArrayTest: XCTestCase {
    func testRemove_Successful() {
        var array = [1, 2, 3]

        array.remove(safeAt: 1)

        XCTAssert(array == [1, 3])
    }

    func testRemove_Failure() {
        var array = [1, 2, 3]

        array.remove(safeAt: 3)

        XCTAssert(array == [1, 2, 3])
    }

    func testInsert_Successful() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 1)

        XCTAssert(array == [1, 4, 2, 3])
    }

    func testInsert_Successful_AtEnd() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 3)

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testInsert_Failure() {
        var array = [1, 2, 3]

        array.insert(4, safeAt: 5)

        XCTAssert(array == [1, 2, 3])
    }

    func testGet_Successful() {
        var array = [1, 2, 3]

        let element = array[safe: 1]

        XCTAssert(element == 2)
    }

    func testGet_Failure() {
        var array = [1, 2, 3]

        let element = array[safe: 4]

        XCTAssert(element == nil)
    }

    func testSet_Successful() {
        var array = [1, 2, 3]

        array[safe: 1] = 4

        XCTAssert(array == [1, 4, 3])
    }

    func testSet_Successful_AtEnd() {
        var array = [1, 2, 3]

        array[safe: 3] = 4

        XCTAssert(array == [1, 2, 3, 4])
    }

    func testSet_Failure() {
        var array = [1, 2, 3]

        array[safe: 4] = 4

        XCTAssert(array == [1, 2, 3])
    }
}

3
extension Array {
  subscript (safe index: UInt) -> Element? {
    return Int(index) < count ? self[Int(index)] : nil
  }
}

Herhangi bir zaman endeksi sınırın dışına çıkarsa, Yukarıda belirtilen uzantı uzantısı nil'in kullanılması.

let fruits = ["apple","banana"]
print("result-\(fruits[safe : 2])")

sonuç - nil


3

Bunun eski bir soru olduğunun farkındayım. Bu noktada Swift5.1 kullanıyorum, OP Swift 1 veya 2 için miydi?

Bugün böyle bir şeye ihtiyacım vardı, ama sadece bir yer için tam ölçekli bir uzantı eklemek istemiyordum ve daha işlevsel bir şey istedim (daha güvenli iş parçacığı?). Ben de sadece bir dizinin sonuna geçmiş olabilir negatif indekslere karşı korumaya gerek yoktu:

let fruit = ["Apple", "Banana", "Coconut"]

let a = fruit.dropFirst(2).first // -> "Coconut"
let b = fruit.dropFirst(0).first // -> "Apple"
let c = fruit.dropFirst(10).first // -> nil

Nil dizileri hakkında tartışanlar için, boş koleksiyonlar için sıfır döndüren özellikler firstve lastözellikler hakkında ne yaparsınız ?

Bunu sevdim çünkü mevcut şeyleri alıp istediğim sonucu elde etmek için kullanabilirdim. Ayrıca dropFirst (n) bir bütün koleksiyon kopyası değil, sadece bir dilim olduğunu biliyorum. Ve sonra zaten var olan ilk davranış benim için devralıyor.


1

Bence bu iyi bir fikir değil. Sınır dışı dizinleri uygulamaya çalışmayla sonuçlanmayan katı kod oluşturmak tercih edilir.

Lütfen bu tür bir hatayı sessizce (yukarıdaki kodunuz tarafından önerildiği gibi) döndürerek başarısız olmanın nildaha da karmaşık, daha zor hataları üretmeye eğilimli olduğunu düşünün .

Geçersiz kılma işleminizi kullandığınız şekilde yapabilirsiniz ve abonelikleri kendi yolunuzla yazabilirsiniz. Tek dezavantajı, mevcut kodun uyumlu olmamasıdır. Genel x [i] 'yi geçersiz kılmak için bir kanca bulmayı düşünüyorum (ayrıca C'deki gibi bir metin ön işlemcisi olmadan) zor olacak.

Düşünebildiğim en yakın şey

// compile error:
if theIndex < str.count && let existing = str[theIndex]

EDIT : Bu gerçekten işe yarıyor. Tek astar !!

func ifInBounds(array: [AnyObject], idx: Int) -> AnyObject? {
    return idx < array.count ? array[idx] : nil
}

if let x: AnyObject = ifInBounds(swiftarray, 3) {
    println(x)
}
else {
    println("Out of bounds")
}

6
Kabul etmiyorum - isteğe bağlı bağlamanın amacı, yalnızca koşul karşılandığında başarılı olmasıdır. (İsteğe bağlı olarak, bir değer olduğu anlamına gelir.) if letBu durumda bir, programı ne daha karmaşık ne de hataları daha zor hale getirmez. Geleneksel iki ififadeli sınır denetimini ve gerçek aramayı tek satırlı, yoğunlaştırılmış bir bildirime yoğunlaştırır. Bir dizin bir sormak gibi, sınırların dışında olması normaldir olduğu (özellikle bir kullanıcı arayüzünde çalışan) durumlar vardır NSTableViewiçin selectedRowbir seçim olmadan.
Craig Otis

3
@Mundi, OP'nin sorusuna bir cevap olmaktan ziyade bir yorum gibi görünüyor.
jlehr

1
@CraigOtis Kabul ettiğimden emin değilim. Sen edebilirsiniz kullanarak "tek hat yoğunlaşmış ifadesi" bir de özlü örneğin bu çek yazmak countElementsveya OP ile yaptığı gibi count, sadece şekilde değil dizi alt simgeler yazma dil tanımlar.
Mundi

1
@jlehr Belki de değil. Ortaya çıkan bir sorunun niyetini veya bilgeliğini sorgulamak adil bir oyundur.
Mundi

2
@Mundi Heh, özellikle daha sonra soruyu gerçekten cevaplamak için düzenlerseniz. :-)
jlehr

1

nilBenim kullanım durumunda s ile dizi yastıklı var :

let components = [1, 2]
var nilComponents = components.map { $0 as Int? }
nilComponents += [nil, nil, nil]

switch (nilComponents[0], nilComponents[1], nilComponents[2]) {
case (_, _, .Some(5)):
    // process last component with 5
default:
    break
}

Ayrıca abonelik uzantısını safe:Erica Sadun / Mike Ash etiketi ile kontrol edin : http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/


0

Swift için "Genel Olarak Reddedilen Değişiklikler" listesi, çökme yerine isteğe bağlı olarak dönmek için Dizi alt simge erişimini değiştirmekten bahseder :

Make Array<T>simge erişim dönüşü T?veya T!yerine T: Mevcut dizisi davranıştır kasıtlı doğru bir out-of-sınırları dizi erişimi bir mantık hatası olduğu gerçeğini yansıtır gibi. Mevcut davranışın değiştirilmesi Arrayerişimleri kabul edilemez bir dereceye düşürecektir . Bu konunun daha önce birkaç kez ortaya çıktığı ancak kabul edilmesi pek mümkün değil.

https://github.com/apple/swift-evolution/blob/master/commonly_proposed.md#strings-characters-and-collection-types

Bu nedenle, temel alt simge erişimi isteğe bağlı olarak değişmeyecektir.

Bununla birlikte, Swift ekibi / topluluğu, bir işlev veya alt simge aracılığıyla Dizilere yeni bir isteğe bağlı dönen erişim modeli eklemeye açık görünüyor .

Bu, burada Swift Evrim forumunda önerilmiş ve tartışılmıştır:

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871

Özellikle, Chris Lattner bu fikri "+1" olarak verdi:

Anlaşılan, bunun için en sık önerilen yazım: yourArray[safe: idx]benim için harika görünüyor. Bunu eklemek için + 1'im.

https://forums.swift.org/t/add-accessor-with-bounds-check-to-array/16871/13

Bu nedenle, Swift'in gelecekteki bazı versiyonlarında kutudan çıkmış olabilir. Swift Evolution iş parçacığına katkıda bulunmasını isteyen herkesi teşvik ederim.


0

İşlemlerin neden başarısız olduğunu yaymak için hatalar seçeneklerden daha iyidir. Abonelikler hata alamaz, bu nedenle bir yöntem olmalıdır.

public extension Collection {
  /// - Returns: same as subscript, if index is in bounds
  /// - Throws: CollectionIndexingError
  func element(at index: Index) throws -> Element {
    guard indices.contains(index)
    else { throw CollectionIndexingError() }

    return self[index]
  }
}

/// Thrown when `element(at:)` is called with an invalid index.
public struct CollectionIndexingError: Error { }
XCTAssertThrowsError( try ["🐾", "🥝"].element(at: 2) )

let optionals = [1, 2, nil]
XCTAssertEqual(try optionals.element(at: 0), 1)

XCTAssertThrowsError( try optionals.element(at: optionals.endIndex) )
{ XCTAssert($0 is CollectionIndexingError) }

0

Neden kimsenin diziyi otomatik olarak büyütmek için ayarlayıcıya sahip bir uzantı koymadığından emin değilim

extension Array where Element: ExpressibleByNilLiteral {
    public subscript(safe index: Int) -> Element? {
        get {
            guard index >= 0, index < endIndex else {
                return nil
            }

            return self[index]
        }

        set(newValue) {
            if index >= endIndex {
                self.append(contentsOf: Array(repeating: nil, count: index - endIndex + 1))
            }

            self[index] = newValue ?? nil
        }
    }
}

Kullanımı kolaydır ve Swift 5.1'den itibaren çalışır

var arr:[String?] = ["A","B","C"]

print(arr) // Output: [Optional("A"), Optional("B"), Optional("C")]

arr[safe:10] = "Z"

print(arr) // [Optional("A"), Optional("B"), Optional("C"), nil, nil, nil, nil, nil, nil, nil, Optional("Z")]

Not: Bir diziyi hızlı bir şekilde büyütürken performans maliyetini (hem zaman hem de alan olarak) anlamalısınız - ancak küçük problemler için bazen Swift'in kendini ayakta kaydırmayı durdurmasını sağlamanız gerekir


-1

Dizi için basit bir uzantı yaptım

extension Array where Iterator.Element : AnyObject {
    func iof (_ i : Int ) -> Iterator.Element? {
        if self.count > i {
            return self[i] as Iterator.Element
        }
        else {
            return nil
        }
    }

}

tasarlandığı gibi mükemmel çalışır

Misal

   if let firstElemntToLoad = roots.iof(0)?.children?.iof(0)?.cNode, 

-1

Swift 5 Kullanımı

extension WKNavigationType {
    var name : String {
        get {
            let names = ["linkAct","formSubm","backForw","reload","formRelo"]
            return names.indices.contains(self.rawValue) ? names[self.rawValue] : "other"
        }
    }
}

ile sona erdi ama gerçekten gibi yapmak istedi

[<collection>][<index>] ?? <default>

ama koleksiyon bağlamsal olduğu için doğru olduğunu düşünüyorum.


Bu cevap kabul edilen cevaptan nasıl farklı? Bana gelince, aynı görünüyor (yinelenen).
Legonaftik

-1

Yalnızca gerektiğinde almak bir diziden değerleri ve küçük bir performans kaybına sakıncası için, (bir çok genel içermeyen bir sözlük tabanlı alternatif yok, (koleksiyonunuzu çok büyük değil yani eğer) benim tat) toplama uzantısı:

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.