ค้นหาอาร์เรย์ที่ปลอดภัย (ตรวจสอบขอบเขต) ใน Swift ผ่านการผูกหรือไม่ก็ได้?


271

หากฉันมีอาร์เรย์ใน Swift และพยายามเข้าถึงดัชนีที่อยู่นอกขอบเขตแสดงว่ามีข้อผิดพลาดรันไทม์ที่ไม่น่าแปลกใจ:

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

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

อย่างไรก็ตามฉันจะคิดด้วยการผูกมัดและความปลอดภัยเสริมทั้งหมดที่ Swift นำมามันคงเป็นเรื่องไม่สำคัญที่จะทำอะไรเช่น:

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

แทน:

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

แต่นี่ไม่ใช่กรณี - ฉันต้องใช้ifคำสั่งol ' เพื่อตรวจสอบและให้แน่ใจว่าดัชนีนั้นน้อยกว่าstr.countคำสั่งให้ตรวจสอบและให้แน่ใจว่าดัชนีมีค่าน้อยกว่า

ฉันพยายามเพิ่มsubscript()การใช้งานของตัวเองแต่ฉันไม่แน่ใจว่าจะส่งการเรียกไปยังการใช้งานดั้งเดิมหรือเข้าถึงรายการ (ตามดัชนี) ได้อย่างไรโดยไม่ต้องใช้เครื่องหมายตัวห้อย:

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

2
ฉันรู้ว่านี่คือ OT เล็กน้อย แต่ฉันก็รู้สึกว่ามันจะดีถ้า Swift มีไวยากรณ์ที่ชัดเจนสำหรับการตรวจสอบขอบเขตทุกประเภทรวมถึงรายการ เรามีคำหลักที่เหมาะสมสำหรับสิ่งนี้แล้วเช่นหาก X ใน (1,2,7) ... หรือถ้า X ใน myArray
Maury Markowitz

คำตอบ:


652

คำตอบของอเล็กซ์มีคำแนะนำและวิธีแก้ปัญหาที่ดีสำหรับคำถามอย่างไรก็ตามฉันบังเอิญพบกับวิธีการใช้งานฟังก์ชั่นที่ดีกว่านี้:

Swift 3.2 และใหม่กว่า

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 และ 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
    }
}

ขอมอบเครดิตให้แก่ Hamish ในการแก้ปัญหาสำหรับ Swift 33

สวิฟท์ 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
    }
}

ตัวอย่าง

let array = [1, 2, 3]

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

45
ฉันคิดว่านี่สมควรได้รับความสนใจอย่างแน่นอน - เป็นงานที่ดี ฉันชอบsafe:ชื่อพารามิเตอร์ที่รวมไว้เพื่อให้แน่ใจว่ามีความแตกต่าง
Craig Otis

11
ในฐานะของ Swift 2 (Xcode 7) สิ่งนี้ต้องการการปรับแต่งเล็กน้อย:return self.indices ~= index ? self[index] : nil;
ทิม

7
เกี่ยวกับสวิฟท์ 3 รุ่น: อาจจะเป็นมุมกรณีเท่านั้นพร้อมรับคำ แต่กระนั้นพรอมต์: มีกรณีที่ "ปลอดภัย" รุ่นที่ห้อยข้างต้นไม่ปลอดภัย (ในขณะที่รุ่นสวิฟท์ 2): สำหรับCollectionประเภทที่Indicesมี ไม่ติดกัน เช่นสำหรับSetกรณีถ้าเราต้องเข้าถึงองค์ประกอบที่ตั้งค่าโดย index ( SetIndex<Element>) เราสามารถเรียกใช้เป็นข้อยกเว้นรันไทม์สำหรับดัชนีที่>= startIndexและ< endIndexในกรณีที่ตัวห้อยปลอดภัยล้มเหลว (ดูตัวอย่างเช่นนี้ contrived )
dfri

12
คำเตือน! การตรวจสอบอาร์เรย์ด้วยวิธีนี้อาจมีราคาแพงมาก containsวิธีจะย้ำผ่านดัชนีทั้งหมดจึงทำให้นี้ O (n) วิธีที่ดีกว่าคือการใช้ดัชนีและนับเพื่อตรวจสอบขอบเขต
Stefan Vasiljevic

6
เพื่อป้องกันการสร้างดัชนีและ iterating เหนือพวกเขา (O (n)) จะดีกว่าที่จะเปรียบเทียบการใช้งาน (O (1)): return index >= startIndex && index < endIndex ? self[index] : nil CollectionประเภทมีstartIndex, ซึ่งเป็นendIndex Comparableแน่นอนว่าสิ่งนี้จะไม่ได้ผลสำหรับคอลเลกชันแปลก ๆ ซึ่งตัวอย่างเช่นไม่มีดัชนีอยู่ตรงกลางการแก้ปัญหาที่มีindicesทั่วไปมากขึ้น
zubko

57

หากคุณต้องการพฤติกรรมนี้มันมีกลิ่นเหมือนคุณต้องการพจนานุกรมแทนการใช้อาเรย์ พจนานุกรมกลับnilเมื่อมีการเข้าถึงกุญแจที่หายไปซึ่งจะทำให้ความรู้สึกเพราะมันเป็นเรื่องยากมากที่จะทราบว่าที่สำคัญมีอยู่ในพจนานุกรมตั้งแต่คีย์เหล่านั้นสามารถเป็นอะไรก็ได้ที่ในอาร์เรย์คีย์ต้องอยู่ในช่วงของ: ไป0 countและเป็นเรื่องธรรมดามากที่จะย้ำช่วงนี้ซึ่งคุณมั่นใจได้อย่างแน่นอนได้มีค่าจริงในการวนซ้ำแต่ละรอบ

ฉันคิดว่าเหตุผลที่มันไม่ทำงานด้วยวิธีนี้เป็นตัวเลือกการออกแบบที่สร้างโดยนักพัฒนา Swift นำตัวอย่างของคุณ:

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

หากคุณรู้อยู่แล้วว่ามีดัชนีอยู่แล้วในกรณีส่วนใหญ่ที่คุณใช้อาร์เรย์รหัสนี้ยอดเยี่ยม แต่ถ้าเข้าถึงห้อยอาจจะกลับมาnilแล้วคุณมีการเปลี่ยนแปลงประเภทกลับของArray's subscriptวิธีการที่จะเป็นตัวเลือก สิ่งนี้จะเปลี่ยนรหัสของคุณเป็น:

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

ซึ่งหมายความว่าคุณจะต้องแกะตัวเลือกใหม่ทุกครั้งที่ทำซ้ำผ่านอาร์เรย์หรือทำสิ่งอื่นใดกับดัชนีที่รู้จักเพียงเพราะคุณไม่ค่อยเข้าถึงดัชนีนอกขอบเขต นักออกแบบของ Swift เลือกที่จะยกเลิกการเลือกออปชั่นน้อยลงโดยมีค่าใช้จ่ายของข้อยกเว้นรันไทม์เมื่อเข้าถึงดัชนีที่ไม่อยู่ในขอบเขต และความผิดพลาดจะดีกว่าข้อผิดพลาดทางตรรกะที่เกิดจากnilคุณไม่ได้คาดหวังในข้อมูลของคุณที่ไหนสักแห่ง

และฉันเห็นด้วยกับพวกเขา ดังนั้นคุณจะไม่เปลี่ยนค่าเริ่มต้นArrayใช้งานเพราะคุณจะทำลายรหัสทั้งหมดที่คาดว่าค่าที่ไม่ใช่ตัวเลือกจากอาร์เรย์

แต่คุณสามารถ subclass Arrayและแทนที่subscriptเพื่อส่งกลับทางเลือก หรือในทางปฏิบัติคุณสามารถขยายArrayด้วยวิธีที่ไม่ใช่ตัวห้อยที่ทำเช่นนี้

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

func get(index: Int) ->T? จะต้องถูกแทนที่ด้วย func get(index: Int) ->Element?


2
+1 (และการยอมรับ) เพื่อกล่าวถึงปัญหาด้วยการเปลี่ยนประเภทการส่งคืนของsubscript()ทางเลือก - นี่เป็นสิ่งกีดขวางบนถนนหลักที่ต้องเผชิญในการเอาชนะพฤติกรรมเริ่มต้น (ผมไม่สามารถเป็นจริงได้รับมันในการทำงานที่ทุกคน. ) ผมก็หลีกเลี่ยงการเขียนget()วิธีขยายซึ่งเป็นทางเลือกที่ชัดเจนในสถานการณ์อื่น ๆ (ประเภท Obj-C, ใคร?) แต่get(ไม่มากมีขนาดใหญ่กว่า[และทำให้มัน ชัดเจนว่าพฤติกรรมอาจแตกต่างจากสิ่งที่นักพัฒนารายอื่นอาจคาดหวังจากตัวดำเนินการตัวห้อยของ Swift ขอบคุณ!
เครกโอทิส

3
เพื่อให้สั้นลงฉันใช้ที่ ();) ขอบคุณ!
hyouuu

7
ในฐานะของสวิฟท์ 2.0 ได้รับการเปลี่ยนชื่อเป็นT Elementเพียงแค่เตือนมิตร :)
Stas ซูคอฟสกี

3
หากต้องการเพิ่มลงในการสนทนานี้อีกเหตุผลหนึ่งที่การตรวจสอบขอบเขตไม่ได้ถูกนำไปไว้ใน Swift เพื่อส่งกลับทางเลือกคือเนื่องจากการส่งคืนnilแทนที่จะทำให้เกิดข้อยกเว้นจากดัชนีนอกขอบเขตจะคลุมเครือ เนื่องจากเช่นArray<String?>สามารถคืนค่าศูนย์ในฐานะสมาชิกที่ถูกต้องของคอลเล็กชันคุณจะไม่สามารถแยกความแตกต่างระหว่างสองกรณีนี้ หากคุณมีประเภทคอลเลกชันของคุณเองที่คุณรู้ว่าไม่สามารถคืนnilค่านั่นคือบริบทของแอปพลิเคชันคุณสามารถขยาย Swift สำหรับการตรวจสอบขอบเขตที่ปลอดภัยตามคำตอบในโพสต์นี้
Aaron

ทำงานได้อย่างสวยงาม
kamyFC

20

เพื่อสร้างคำตอบของ Nikita Kukushkin บางครั้งคุณจำเป็นต้องกำหนดให้กับดัชนีอาเรย์รวมทั้งอ่านจากพวกเขาอย่างปลอดภัยเช่น

myArray[safe: badIndex] = newValue

ดังนั้นนี่คือการอัปเดตคำตอบของ Nikita (Swift 3.2) ที่อนุญาตให้เขียนดัชนีอาเรย์ที่ไม่แน่นอนได้อย่างปลอดภัยโดยเพิ่มชื่อพารามิเตอร์ที่ปลอดภัย

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
คำตอบที่ underrated มาก! นี่เป็นวิธีที่ถูกต้องในการทำสิ่งนี้!
Reid

14

มีผลบังคับใช้ใน Swift 2

แม้ว่านี่จะได้รับคำตอบมาหลายครั้งแล้ว แต่ฉันอยากจะนำเสนอคำตอบเพิ่มเติมในแบบที่การเขียนโปรแกรมของ Swift กำลังดำเนินไป protocolเป็นครั้งแรก"

•เราต้องการทำอะไร
- รับองค์ประกอบของArrayดัชนีที่กำหนดเฉพาะเมื่อปลอดภัยและnilอื่น ๆ
•ฟังก์ชันการทำงานนี้ควรใช้กับอะไร
- Array subscripting
•ฟีเจอร์นี้มาจากไหน?
- คำจำกัดความของมันstruct ArrayในSwiftโมดูลมี
•ไม่มีอะไรเพิ่มเติมที่เป็นนามธรรม /?
- มันใช้protocol CollectionTypeซึ่งทำให้มั่นใจได้เช่นกัน
•ไม่มีอะไรเพิ่มเติมทั่วไป / นามธรรม?
- มันใช้protocol Indexableเช่นกัน ...
•ใช่เสียงเหมือนที่ดีที่สุดที่เราสามารถทำได้ จากนั้นเราสามารถขยายให้มีคุณสมบัติที่เราต้องการได้หรือไม่
- ) ทำงานกับตอนนี้!แต่เรามีประเภทที่ จำกัด มาก (ไม่Int) และคุณสมบัติ (ไม่ใช่count
•มันจะเพียงพอ stdlib ของ Swift ทำได้ค่อนข้างดี;)

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

¹: ไม่จริง แต่มันให้ความคิด


2
ในฐานะมือใหม่ที่รวดเร็วฉันไม่เข้าใจคำตอบนี้ รหัสท้ายสุดหมายถึงอะไร นั่นเป็นวิธีแก้ปัญหาหรือไม่และถ้าเป็นเช่นนั้นฉันจะใช้งานจริงได้อย่างไร
Thomas Tempelmann

3
ขออภัยคำตอบนี้ไม่ถูกต้องสำหรับ Swift 3 อีกต่อไป แต่กระบวนการนี้แน่นอน แตกต่างเพียงว่าตอนนี้คุณควรจะหยุดที่Collectionอาจ :)
DeFrenZ

11
extension Array {
    subscript (safe index: Index) -> Element? {
        return 0 <= index && index < count ? self[index] : nil
    }
}
  • ประสิทธิภาพ O (1)
  • พิมพ์ปลอดภัย
  • จัดการกับ Optionals สำหรับ [MyType?] อย่างถูกต้อง (ส่งคืน MyType ?? ที่สามารถเปิดทั้งสองระดับได้)
  • ไม่นำไปสู่ปัญหาสำหรับชุด
  • รหัสย่อ

นี่คือการทดสอบบางอย่างที่ฉันพบเพื่อคุณ:

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
  • เนื่องจากอาร์เรย์อาจเก็บค่าศูนย์ดังนั้นจึงไม่มีเหตุผลที่จะคืนค่าศูนย์ถ้าการเรียกอาร์เรย์ [ดัชนี] ไม่อยู่ในขอบเขต
  • เนื่องจากเราไม่ทราบว่าผู้ใช้ต้องการจัดการกับปัญหาขอบเขตได้อย่างไรจึงไม่มีเหตุผลที่จะใช้ตัวดำเนินการที่กำหนดเอง
  • ในทางตรงกันข้ามให้ใช้โฟลว์ควบคุมแบบดั้งเดิมเพื่อแยกชิ้นส่วนวัตถุออกและให้ความปลอดภัยในการพิมพ์

ถ้าให้ index = array.checkIndexForSafety (index: Int)

  let item = array[safeIndex: index] 

ถ้าให้ 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
แนวทางที่น่าสนใจ เหตุผลใดที่SafeIndexเป็นคลาสและไม่เป็น struct หรือไม่?
stef

8

สวิฟต์ 4

ส่วนขยายสำหรับผู้ที่ชอบไวยากรณ์แบบดั้งเดิมมากขึ้น:

extension Array {

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

คุณไม่จำเป็นต้อง จำกัด องค์ประกอบของอาร์เรย์ให้เท่ากันเพื่อตรวจสอบว่าดัชนีมีดัชนีหรือไม่
Leo Dabus

ใช่ - จุดดี - มันจะต้องใช้สำหรับวิธีการที่ปลอดภัยเพิ่มเติมเช่น deleteObject ฯลฯ
Matjan

5

ฉันพบอาร์เรย์ที่ปลอดภัยรับตั้งแทรกลบมีประโยชน์มาก ฉันชอบที่จะเข้าสู่ระบบและไม่สนใจข้อผิดพลาดเพราะทุกอย่างยากที่จะจัดการ ร้องเต็มรหัส

/**
 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)
            }
        }
    }
}

การทดสอบ

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

การใช้ส่วนบนดังกล่าวข้างต้นกลับไม่มีศูนย์ถ้าทุกครั้งที่ดัชนีหลุด

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

ผล - ศูนย์


3

ฉันรู้ว่านี่เป็นคำถามเก่า ฉันใช้ Swift5.1 ณ จุดนี้ OP สำหรับ Swift 1 หรือ 2 หรือไม่

ฉันต้องการสิ่งนี้ในวันนี้ แต่ฉันไม่ต้องการเพิ่มส่วนขยายแบบเต็มสำหรับที่เดียวและต้องการสิ่งที่ใช้งานได้มากขึ้น (ปลอดภัยมากขึ้นไหม?) ฉันยังไม่จำเป็นต้องป้องกันดัชนีลบเพียงแค่สิ่งที่อาจผ่านจุดสิ้นสุดของอาร์เรย์:

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

สำหรับคนที่โต้เถียงเกี่ยวกับ Sequence with nil คุณจะทำอะไรเกี่ยวกับfirstและlastคุณสมบัติคุณสมบัติที่คืนค่าศูนย์สำหรับคอลเลกชันที่ว่างเปล่า

ฉันชอบสิ่งนี้เพราะฉันสามารถคว้าสิ่งที่มีอยู่และใช้เพื่อให้ได้ผลลัพธ์ที่ฉันต้องการ ฉันรู้ด้วยว่า dropFirst (n) ไม่ใช่สำเนาทั้งหมดของคอลเลคชั่น และจากนั้นพฤติกรรมที่มีอยู่แล้วของการครอบครองครั้งแรกสำหรับฉัน


1

ฉันคิดว่านี่ไม่ใช่ความคิดที่ดี ดูเหมือนว่าจะสร้างโค้ดแบบทึบซึ่งไม่ส่งผลให้พยายามใช้ดัชนีนอกขอบเขต

โปรดพิจารณาว่าการมีข้อผิดพลาดดังกล่าวล้มเหลวอย่างเงียบ ๆ (ตามรหัสของคุณด้านบน) โดยส่งคืน nilมักจะทำให้เกิดข้อผิดพลาดที่ซับซ้อนยิ่งขึ้นและยากขึ้น

คุณสามารถแทนที่ของคุณในแบบเดียวกันกับที่คุณใช้และเพียงแค่เขียนตัวห้อยในแบบของคุณเอง ข้อเสียเปรียบเท่านั้นคือรหัสที่มีอยู่จะไม่สามารถทำงานร่วมกันได้ ฉันคิดว่าการหาตะขอเพื่อแทนที่ x ทั่วไป (i) (เช่นกันโดยไม่มีตัวประมวลผลข้อความเหมือนกับใน C) จะเป็นสิ่งที่ท้าทาย

สิ่งที่ฉันคิดได้ใกล้เคียงที่สุดคือ

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

แก้ไข : ใช้งานได้จริง หนึ่งในสายการบิน!!

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
ฉันไม่เห็นด้วย - จุดผูกมัดทางเลือกคือว่าจะประสบความสำเร็จก็ต่อเมื่อตรงตามเงื่อนไขเท่านั้น (สำหรับทางเลือกหมายถึงมีค่า) การใช้if letในกรณีนี้ไม่ได้ทำให้โปรแกรมซับซ้อนขึ้นหรือข้อผิดพลาดยากขึ้น มันแค่ควบแน่นการifตรวจสอบขอบเขตสองคำสั่งดั้งเดิมและการค้นหาที่แท้จริงลงในคำสั่งที่มีเส้นเดียว มีบางกรณี (โดยเฉพาะอย่างยิ่งการทำงานใน UI) การที่มันเป็นเรื่องปกติสำหรับดัชนีที่จะออกจากขอบเขตเช่นขอให้บริการNSTableViewสำหรับselectedRowโดยไม่ต้องเลือก
เครกโอทิส

3
@Mundi นี้ดูเหมือนว่าจะเป็นความคิดเห็นมากกว่าคำตอบสำหรับคำถามของ OP
กรกฎาคม

1
@ CraigOtis ไม่แน่ใจว่าฉันเห็นด้วย คุณสามารถเขียนเช็คนี้อย่างชัดเจนใน "บรรทัดเดียว, คำสั่งย่อ" เช่นการใช้countElementsหรือเป็น OP ทำด้วยcountไม่เพียงในวิธีที่ภาษากำหนดการเขียนตัวห้อยอาร์เรย์
Mundi

1
@ jlehr อาจจะไม่ มันเป็นเกมที่ยุติธรรมที่จะถามถึงเจตนาหรือภูมิปัญญาของปัญหาที่เกิดขึ้น
Mundi

2
@ Mundi Heh โดยเฉพาะถ้าคุณแก้ไขในภายหลังเพื่อตอบคำถาม :-)
jlehr

1

ฉันได้เบาะอาเรย์กับnils ในกรณีที่ใช้ของฉัน:

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
}

ตรวจสอบส่วนขยายตัวห้อยพร้อมsafe:ป้ายกำกับโดย Erica Sadun / Mike Ash: http://ericasadun.com/2015/06/01/swift-safe-array-indexing-my-favorite-thing-of-the-new-week/


0

รายการ "การเปลี่ยนแปลงที่ถูกปฏิเสธโดยทั่วไป" สำหรับรายการ Swift มีการกล่าวถึงการเปลี่ยนการเข้าถึงตัวห้อยของArray เพื่อส่งคืนทางเลือกแทนที่จะหยุดทำงาน:

ทำให้Array<T>ผลตอบแทนการเข้าถึงห้อยT?หรือT!แทนT: พฤติกรรมอาร์เรย์ปัจจุบันมีเจตนาเพราะมันสะท้อนให้เห็นถึงความจริงที่ว่าการเข้าถึงอาร์เรย์นอกขอบเขตเป็นข้อผิดพลาดเชิงตรรกะอย่างถูกต้อง การเปลี่ยนพฤติกรรมปัจจุบันจะทำให้การArrayเข้าถึงระดับที่ยอมรับไม่ได้ช้าลง หัวข้อนี้เกิดขึ้นหลายครั้งก่อนหน้า แต่ไม่น่าจะยอมรับได้

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

ดังนั้นการเข้าถึงตัวห้อยพื้นฐานจะไม่เปลี่ยนแปลงเพื่อส่งกลับทางเลือก

อย่างไรก็ตามทีม / ชุมชน Swift ดูเหมือนจะเปิดให้เพิ่มรูปแบบการเข้าถึงใหม่ที่เป็นทางเลือกกลับไปยังอาร์เรย์โดยผ่านฟังก์ชั่นหรือตัวห้อย

สิ่งนี้ได้ถูกเสนอและพูดคุยในฟอรัม Swift Evolution ที่นี่:

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

โดยเฉพาะอย่างยิ่ง Chris Lattner ให้แนวคิด "+1":

เห็นด้วยการสะกดคำที่แนะนำบ่อยที่สุดสำหรับเรื่องนี้คือ: yourArray[safe: idx]ซึ่งดูดีสำหรับฉัน ฉัน +1 มากสำหรับการเพิ่มนี้

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

ดังนั้นนี่อาจเป็นไปได้นอกกรอบใน Swift บางเวอร์ชันในอนาคต ฉันขอแนะนำให้ทุกคนที่ต้องการให้มันมีส่วนร่วมในหัวข้อ Swift Evolution


0

เพื่อเผยแพร่สาเหตุที่การดำเนินการล้มเหลวข้อผิดพลาดจะดีกว่าตัวเลือก ตัวห้อยไม่สามารถโยนข้อผิดพลาดได้ดังนั้นจึงต้องเป็นวิธีการ

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

ไม่แน่ใจว่าทำไมไม่มีใครใส่ส่วนขยายที่มีตัวตั้งค่าเพื่อขยายอาร์เรย์โดยอัตโนมัติ

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

การใช้งานง่ายและใช้งานได้ตั้งแต่ Swift 5.1

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")]

หมายเหตุ: คุณควรเข้าใจค่าใช้จ่ายด้านประสิทธิภาพ (ทั้งในเวลา / สถานที่) เมื่อเพิ่มจำนวนอาเรย์อย่างรวดเร็ว - แต่สำหรับปัญหาเล็ก ๆ บางครั้งคุณต้องใช้สวิฟท์เพื่อหยุดยั้งการขยับตัวเอง


-1

ฉันได้ทำการขยายง่าย ๆ สำหรับอาร์เรย์

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

}

มันทำงานได้อย่างสมบูรณ์แบบตามที่ออกแบบไว้

ตัวอย่าง

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

-1

การใช้งานSwift 5

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

จบลงด้วย แต่จริงๆอยากจะทำกันโดยทั่วไปเช่น

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

แต่เนื่องจากคอลเลกชันเป็นบริบทฉันคิดว่ามันเหมาะสม


คำตอบนี้ต่างจากคำตอบที่ยอมรับได้อย่างไร สำหรับฉันมันดูเหมือนกันทุกประการ (ซ้ำกัน)
Legonaftik

-1

เมื่อคุณต้องการรับค่าจากอาเรย์เท่านั้นและคุณไม่ต้องกังวลว่าจะได้รับโทษเล็ก ๆ น้อย ๆ (เช่นถ้าคอลเลกชันของคุณไม่ใหญ่มาก) มีทางเลือกพจนานุกรมที่ไม่เกี่ยวข้อง (ทั่วไปเกินไปสำหรับฉัน รสชาติ) การขยายคอลเลกชัน:

// Assuming you have a collection named array:
let safeArray = Dictionary(uniqueKeysWithValues: zip(0..., array))
let value = safeArray[index] ?? defaultValue;
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.