String.Index ทำงานอย่างไรใน Swift


109

ฉันได้อัปเดตโค้ดและคำตอบเก่าของฉันด้วย Swift 3 แต่เมื่อฉันไปที่ Swift Strings และ Indexing มันเป็นเรื่องยากที่จะเข้าใจสิ่งต่างๆ

โดยเฉพาะฉันกำลังลองสิ่งต่อไปนี้:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5) // error

โดยที่บรรทัดที่สองทำให้ฉันมีข้อผิดพลาดต่อไปนี้

'advancedBy' ไม่พร้อมใช้งาน: ในการเลื่อนดัชนีโดย n ขั้นตอนให้เรียก 'index (_: offsetBy :)' บนอินสแตนซ์ CharacterView ที่สร้างดัชนี

ฉันเห็นว่าStringมีวิธีการดังต่อไปนี้

str.index(after: String.Index)
str.index(before: String.Index)
str.index(String.Index, offsetBy: String.IndexDistance)
str.index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

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

คำตอบ:


282

ป้อนคำอธิบายภาพที่นี่

ตัวอย่างทั้งหมดต่อไปนี้ใช้

var str = "Hello, playground"

startIndex และ endIndex

  • startIndex คือดัชนีของอักขระตัวแรก
  • endIndexคือดัชนีหลังอักขระสุดท้าย

ตัวอย่าง

// character
str[str.startIndex] // H
str[str.endIndex]   // error: after last character

// range
let range = str.startIndex..<str.endIndex
str[range]  // "Hello, playground"

ด้วยช่วงด้านเดียวของ Swift 4 ช่วงสามารถทำให้ง่ายขึ้นเป็นหนึ่งในรูปแบบต่อไปนี้

let range = str.startIndex...
let range = ..<str.endIndex

ฉันจะใช้แบบฟอร์มเต็มในตัวอย่างต่อไปนี้เพื่อความชัดเจน แต่เพื่อประโยชน์ในการอ่านคุณอาจต้องการใช้ช่วงด้านเดียวในโค้ดของคุณ

after

ใน: index(after: String.Index)

  • after หมายถึงดัชนีของอักขระโดยตรงหลังดัชนีที่กำหนด

ตัวอย่าง

// character
let index = str.index(after: str.startIndex)
str[index]  // "e"

// range
let range = str.index(after: str.startIndex)..<str.endIndex
str[range]  // "ello, playground"

before

ใน: index(before: String.Index)

  • before หมายถึงดัชนีของอักขระโดยตรงก่อนดัชนีที่กำหนด

ตัวอย่าง

// character
let index = str.index(before: str.endIndex)
str[index]  // d

// range
let range = str.startIndex..<str.index(before: str.endIndex)
str[range]  // Hello, playgroun

offsetBy

ใน: index(String.Index, offsetBy: String.IndexDistance)

  • offsetByค่าสามารถบวกหรือเชิงลบและเริ่มต้นจากดัชนีที่กำหนด แม้ว่าจะเป็นประเภทใดString.IndexDistanceก็ตามคุณสามารถระบุไฟล์Int.

ตัวอย่าง

// character
let index = str.index(str.startIndex, offsetBy: 7)
str[index]  // p

// range
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
str[range]  // play

limitedBy

ใน: index(String.Index, offsetBy: String.IndexDistance, limitedBy: String.Index)

  • สิ่งlimitedByนี้มีประโยชน์ในการตรวจสอบให้แน่ใจว่าการชดเชยไม่ทำให้ดัชนีหลุดออกไปนอกขอบเขต มันเป็นดัชนีขอบเขต เนื่องจากเป็นไปได้ที่ค่าชดเชยจะเกินขีด จำกัด เมธอดนี้จะส่งคืนตัวเลือก จะส่งคืนnilหากดัชนีอยู่นอกขอบเขต

ตัวอย่าง

// character
if let index = str.index(str.startIndex, offsetBy: 7, limitedBy: str.endIndex) {
    str[index]  // p
}

หากได้รับการชดเชย77แทน7แล้วifคำสั่งจะได้รับข้าม

ทำไมต้องใช้ String.Index?

การใช้ดัชนีสำหรับ Strings จะง่ายกว่ามาก Intเหตุผลที่คุณต้องสร้างใหม่String.Indexสำหรับทุก String คือตัวอักษรใน Swift มีความยาวไม่เท่ากันทั้งหมดภายใต้ประทุน อักขระ Swift ตัวเดียวอาจประกอบด้วยจุดรหัส Unicode หนึ่งสองจุดหรือมากกว่านั้น ดังนั้นแต่ละสตริงที่ไม่ซ้ำกันจะต้องคำนวณดัชนีของอักขระ

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


18
ทำไมต้องstartIndexเป็นอย่างอื่นที่ไม่ใช่ 0?
Robo Robok

15
@RoboRobok: เนื่องจาก Swift ทำงานร่วมกับอักขระ Unicode ซึ่งสร้างจาก "grapheme clusters" Swift จึงไม่ใช้จำนวนเต็มเพื่อแสดงตำแหน่งดัชนี สมมติว่าตัวละครแรกของคุณคือéไฟล์. มันถูกสร้างขึ้นจากการeบวก\u{301}Unicode แทน หากคุณใช้ดัชนีเป็นศูนย์คุณจะได้รับeอักขระเน้นเสียง ("หลุมฝังศพ") ไม่ใช่ทั้งคลัสเตอร์ที่ประกอบเป็น é. การใช้startIndexเพื่อให้แน่ใจว่าคุณจะได้รับคลัสเตอร์กราฟฟีมทั้งหมดสำหรับอักขระใด ๆ
ลีแอนน์

2
ใน Swift 4.0 อักขระ Unicode แต่ละตัวจะนับด้วย 1 เช่น: "👩‍💻" .count // Now: 1, Before: 2
selva

3
เราสร้าง a String.Indexจากจำนวนเต็มได้อย่างไรนอกเหนือจากการสร้างสตริงจำลองและใช้.indexวิธีการกับมัน? ฉันไม่รู้ว่าฉันขาดอะไรไป แต่เอกสารไม่ได้พูดอะไรเลย
sudo

3
@sudo คุณต้องระวังเล็กน้อยเมื่อสร้าง a String.Indexด้วยจำนวนเต็มเพราะ Swift แต่ละตัวCharacterไม่จำเป็นต้องเท่ากับสิ่งที่คุณหมายถึงด้วยจำนวนเต็ม คุณสามารถส่งจำนวนเต็มเข้าไปในoffsetByพารามิเตอร์เพื่อสร้างไฟล์String.Index. หากคุณไม่มีคุณจะไม่Stringสามารถสร้าง a ได้String.Index(เนื่องจาก Swift สามารถคำนวณดัชนีได้ก็ต่อเมื่อรู้ว่าอักขระก่อนหน้าในสตริงคืออะไร) หากคุณเปลี่ยนสตริงคุณต้องคำนวณดัชนีใหม่ คุณไม่สามารถใช้สิ่งเดียวกันString.Indexกับสองสตริงที่ต่างกัน
Suragch

0
func change(string: inout String) {

    var character: Character = .normal

    enum Character {
        case space
        case newLine
        case normal
    }

    for i in stride(from: string.count - 1, through: 0, by: -1) {
        // first get index
        let index: String.Index?
        if i != 0 {
            index = string.index(after: string.index(string.startIndex, offsetBy: i - 1))
        } else {
            index = string.startIndex
        }

        if string[index!] == "\n" {

            if character != .normal {

                if character == .newLine {
                    string.remove(at: index!)
                } else if character == .space {
                    let number = string.index(after: string.index(string.startIndex, offsetBy: i))
                    if string[number] == " " {
                        string.remove(at: number)
                    }
                    character = .newLine
                }

            } else {
                character = .newLine
            }

        } else if string[index!] == " " {

            if character != .normal {

                string.remove(at: index!)

            } else {
                character = .space
            }

        } else {

            character = .normal

        }

    }

    // startIndex
    guard string.count > 0 else { return }
    if string[string.startIndex] == "\n" || string[string.startIndex] == " " {
        string.remove(at: string.startIndex)
    }

    // endIndex - here is a little more complicated!
    guard string.count > 0 else { return }
    let index = string.index(before: string.endIndex)
    if string[index] == "\n" || string[index] == " " {
        string.remove(at: index)
    }

}

-2

สร้าง UITextView ภายใน tableViewController ฉันใช้ฟังก์ชัน: textViewDidChange จากนั้นตรวจสอบการป้อนกลับคีย์ จากนั้นหากตรวจพบ return-key-input ให้ลบอินพุตของปุ่ม return และปิดคีย์บอร์ด

func textViewDidChange(_ textView: UITextView) {
    tableView.beginUpdates()
    if textView.text.contains("\n"){
        textView.text.remove(at: textView.text.index(before: textView.text.endIndex))
        textView.resignFirstResponder()
    }
    tableView.endUpdates()
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.