Menemukan indeks karakter di Swift String


203

Sudah waktunya untuk mengakui kekalahan ...

Di Objective-C, saya bisa menggunakan sesuatu seperti:

NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2

Di Swift, saya melihat sesuatu yang serupa:

var str = "abcdefghi"
str.rangeOfString("c").startIndex

... tapi itu hanya memberi saya String.Index, yang bisa saya gunakan untuk berlangganan kembali ke string asli, tetapi tidak mengambil lokasi dari.

FWIW, yang String.Indexmemiliki ivar pribadi yang disebut _positionmemiliki nilai yang benar di dalamnya. Aku hanya tidak melihat bagaimana itu terlihat.

Saya tahu saya bisa dengan mudah menambahkan ini ke String sendiri. Saya lebih ingin tahu tentang apa yang saya lewatkan di API baru ini.


Berikut ini adalah proyek GitHub yang berisi banyak metode ekstensi untuk manipulasi string Swift: github.com/iamjono/SwiftString
RenniePet

Implementasi terbaik yang saya temukan ada di sini: stackoverflow.com/a/32306142/4550651
Carlos GarcΓ­a

Apakah Anda perlu membedakan antara Unicode Codepoints dan Extended Grapheme Clusters?
Ben Leggiero

Jawaban:


248

Anda bukan satu-satunya yang tidak dapat menemukan solusinya.

Stringtidak menerapkan RandomAccessIndexType. Mungkin karena mereka mengaktifkan karakter dengan panjang byte yang berbeda. Itu sebabnya kita harus menggunakan string.characters.count( countatau countElementsdalam Swift 1.x) untuk mendapatkan jumlah karakter. Itu juga berlaku untuk posisi. The _positionmungkin indeks ke dalam array mentah byte dan mereka tidak ingin mengekspos itu. Ini String.Indexdimaksudkan untuk melindungi kita dari mengakses byte di tengah karakter.

Itu berarti bahwa indeks apa pun yang Anda dapatkan harus dibuat dari String.startIndexatau String.endIndex( String.Indexmengimplementasikan BidirectionalIndexType). Indeks lainnya dapat dibuat menggunakan successoratau predecessormetode.

Sekarang untuk membantu kami dengan indeks, ada satu set metode (fungsi di Swift 1.x):

Swift 4.x

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 3.0

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

Swift 1.x

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
let index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times

Bekerja dengan String.Indexitu rumit tetapi menggunakan pembungkus untuk mengindeks oleh bilangan bulat (lihat https://stackoverflow.com/a/25152652/669586 ) berbahaya karena menyembunyikan inefisiensi pengindeksan nyata.

Perhatikan bahwa implementasi pengindeksan Swift memiliki masalah yang indeks / rentang yang dibuat untuk satu string tidak dapat diandalkan digunakan untuk string yang berbeda , misalnya:

Swift 2.x

let text: String = "abc"
let text2: String = "πŸŽΎπŸ‡πŸˆ"

let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

Swift 1.x

let text: String = "abc"
let text2: String = "πŸŽΎπŸ‡πŸˆ"

let range = text.rangeOfString("b")

//can randomly return nil or a bad substring 
let substring: String = text2[range] 

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)    
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]  

1
Mungkin canggung, mungkin ini jawabannya. Mudah-mudahan dua fungsi rentang tersebut akan masuk ke dalam dokumentasi sebelum rilis final.
Matt Wilding

Jenis apa yang ada rangedivar range = text.rangeOfString("b")
zaph

5
@ Zak Setiap orang Collectionmemiliki typealias IndexType. Untuk array, didefinisikan sebagai Int, untuk Stringdidefinisikan sebagai String.Index. Baik array dan string juga dapat menggunakan rentang (untuk membuat subarrays dan substring). Rentang ini merupakan tipe khusus Range<T>. Untuk string, ini Range<String.Index>, untuk array Range<Int>.
Sulthan

1
In Swift 2.0, distance(text.startIndex, range.startIndex)menjaditext.startIndex.distanceTo(range.startIndex)
superarts.org

1
@ Devios String, persis seperti NSStringdi Yayasan memiliki metode yang disebut hasPrefix(_:).
Sulthan

86

Swift 3.0 membuat ini lebih verbose:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.index(of: needle) {
    let pos = string.characters.distance(from: string.startIndex, to: idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Perpanjangan:

extension String {
    public func index(of char: Character) -> Int? {
        if let idx = characters.index(of: char) {
            return characters.distance(from: startIndex, to: idx)
        }
        return nil
    }
}

Di Swift 2.0 ini menjadi lebih mudah:

let string = "Hello.World"
let needle: Character = "."
if let idx = string.characters.indexOf(needle) {
    let pos = string.startIndex.distanceTo(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

Perpanjangan:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = self.characters.indexOf(char) {
            return self.startIndex.distanceTo(idx)
        }
        return nil
    }
}

Implementasi Swift 1.x:

Untuk solusi Swift murni, seseorang dapat menggunakan:

let string = "Hello.World"
let needle: Character = "."
if let idx = find(string, needle) {
    let pos = distance(string.startIndex, idx)
    println("Found \(needle) at position \(pos)")
}
else {
    println("Not found")
}

Sebagai perpanjangan ke String:

extension String {
    public func indexOfCharacter(char: Character) -> Int? {
        if let idx = find(self, char) {
            return distance(self.startIndex, idx)
        }
        return nil
    }
}

1
karakter sudah usang !!
Shivam Pokhriyal

23
extension String {

    // MARK: - sub String
    func substringToIndex(index:Int) -> String {
        return self.substringToIndex(advance(self.startIndex, index))
    }
    func substringFromIndex(index:Int) -> String {
        return self.substringFromIndex(advance(self.startIndex, index))
    }
    func substringWithRange(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
        let end = advance(self.startIndex, range.endIndex)
        return self.substringWithRange(start..<end)
    }

    subscript(index:Int) -> Character{
        return self[advance(self.startIndex, index)]
    }
    subscript(range:Range<Int>) -> String {
        let start = advance(self.startIndex, range.startIndex)
            let end = advance(self.startIndex, range.endIndex)
            return self[start..<end]
    }


    // MARK: - replace
    func replaceCharactersInRange(range:Range<Int>, withString: String!) -> String {
        var result:NSMutableString = NSMutableString(string: self)
        result.replaceCharactersInRange(NSRange(range), withString: withString)
        return result
    }
}

7
Berpikir untuk melakukan ini, tapi saya pikir itu adalah masalah yang menyembunyikan semantik akses string. Bayangkan membuat API untuk mengakses daftar tertaut yang terlihat seperti API untuk array. Orang ingin menulis kode yang sangat tidak efisien.
Erik Engheim

16

Saya telah menemukan solusi ini untuk swift2:

var str = "abcdefghi"
let indexForCharacterInString = str.characters.indexOf("c") //returns 2

apa yang akan menjadi indeks ketika str = "abcdefgchi"
Vatsal Shukla

10

Swift 5.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return firstIndex(of: char)?.utf16Offset(in: self)
  }
}

Cepat 4.0

public extension String {  
  func indexInt(of char: Character) -> Int? {
    return index(of: char)?.encodedOffset        
  }
}

indeks pengembalian (dari: elemen) .map {target.distance (dari: startIndex, ke: $ 0)}
frogcjn

8

Saya tidak yakin bagaimana cara mengekstrak posisi dari String.Index, tetapi jika Anda bersedia untuk kembali ke beberapa kerangka kerja Objective-C, Anda dapat menjembatani ke tujuan-c dan melakukannya dengan cara yang sama seperti dulu.

"abcdefghi".bridgeToObjectiveC().rangeOfString("c").location

Sepertinya beberapa metode NSString belum (atau mungkin tidak) porting ke String. Berisi juga muncul di pikiran.


Ini benar-benar muncul bahwa mengakses properti lokasi nilai kembali adalah cukup untuk compiler untuk menyimpulkan jenis NSString, sehingga bridgeToObjectiveC()panggilan tidak diperlukan. Masalah saya tampaknya hanya muncul ketika memanggil rangeOfStringSwift String yang sudah ada sebelumnya. Sepertinya masalah API ...
Matt Wilding

Itu menarik. Saya tidak tahu itu disimpulkan dalam kasus-kasus itu. Nah saat itu sudah menjadi String Anda selalu dapat menggunakan jembatan.
Connor

8

Berikut ini adalah ekstensi String bersih yang menjawab pertanyaan:

Swift 3:

extension String {
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).range(of: target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).range(of: target, options: NSString.CompareOptions.backwards)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.range(of: s) != nil) ? true : false
    }
}

Swift 2.2:

extension String {    
    var length:Int {
        return self.characters.count
    }

    func indexOf(target: String) -> Int? {

        let range = (self as NSString).rangeOfString(target)

        guard range.toRange() != nil else {
            return nil
        }

        return range.location

    }
    func lastIndexOf(target: String) -> Int? {



        let range = (self as NSString).rangeOfString(target, options: NSStringCompareOptions.BackwardsSearch)

        guard range.toRange() != nil else {
            return nil
        }

        return self.length - range.location - 1

    }
    func contains(s: String) -> Bool {
        return (self.rangeOfString(s) != nil) ? true : false
    }
}

7

Anda juga dapat menemukan indeks karakter dalam string tunggal seperti ini,

extension String {

  func indexes(of character: String) -> [Int] {

    precondition(character.count == 1, "Must be single character")

    return self.enumerated().reduce([]) { partial, element  in
      if String(element.element) == character {
        return partial + [element.offset]
      }
      return partial
    }
  }

}

Yang memberikan hasil dalam [String.Distance] yaitu. [Int], seperti

"apple".indexes(of: "p") // [1, 2]
"element".indexes(of: "e") // [0, 2, 4]
"swift".indexes(of: "j") // []

5

Jika Anda ingin menggunakan NSString yang sudah dikenal, Anda dapat mendeklarasikannya secara eksplisit:

var someString: NSString = "abcdefghi"

var someRange: NSRange = someString.rangeOfString("c")

Saya belum yakin bagaimana melakukan ini di Swift.


1
Ini tentu berhasil, dan tampaknya kompiler cukup agresif dalam menyimpulkan tipe NSString untuk Anda. Saya benar-benar berharap cara Swift yang murni dalam melakukan ini, karena sepertinya ini adalah kasus penggunaan yang cukup umum.
Matt Wilding

Ya, saya melihat sekeliling, tetapi saya tidak melihatnya. Mungkin saja mereka fokus pada area yang tidak didukung oleh ObjC karena mereka dapat mengisi kesenjangan ini tanpa kehilangan banyak kemampuan. Hanya berpikir keras-keras :)
Logan

4

Jika Anda ingin mengetahui posisi dari karakter di string sebagai int penggunaan nilai ini:

let loc = newString.range(of: ".").location

Swift 5: value of type 'Range<String.Index>?' has no member 'location'.
Neph

3

Saya tahu ini sudah lama dan jawaban telah diterima, tetapi Anda dapat menemukan indeks string dalam beberapa baris kode menggunakan:

var str : String = "abcdefghi"
let characterToFind: Character = "c"
let characterIndex = find(str, characterToFind)  //returns 2

Beberapa informasi hebat lainnya tentang string Swift di sini Strings in Swift


findtidak tersedia di Swift 3
AamirR

2

Ini berhasil untuk saya,

var loc = "abcdefghi".rangeOfString("c").location
NSLog("%d", loc);

ini bekerja juga,

var myRange: NSRange = "abcdefghi".rangeOfString("c")
var loc = myRange.location
NSLog("%d", loc);

Keduanya tampaknya mundur pada perilaku NSString entah bagaimana. Di taman bermain saya, saya menggunakan variabel perantara untuk string. var str = "abcdef"; str.rangeOfString("c").locationmemunculkan kesalahan tentang String.Index tidak memiliki lokasi bernama anggota ...
Matt Wilding

1
Ini berfungsi hanya karena "string" adalah NSString dan bukan String Swift
gderaco

2

Tipe variabel String di Swift berisi fungsi yang berbeda dibandingkan dengan NSString di Objective-C. Dan seperti yang disebutkan Sulthan,

Swift String tidak menerapkan RandomAccessIndex

Apa yang dapat Anda lakukan adalah menurunkan variabel Anda dari tipe String ke NSString (ini berlaku di Swift). Ini akan memberi Anda akses ke fungsi di NSString.

var str = "abcdefghi" as NSString
str.rangeOfString("c").locationx   // returns 2

2

Jika Anda memikirkannya, Anda sebenarnya tidak benar-benar membutuhkan versi Int lokasi yang tepat. Rentang atau bahkan String .Index cukup untuk mendapatkan substring lagi jika diperlukan:

let myString = "hello"

let rangeOfE = myString.rangeOfString("e")

if let rangeOfE = rangeOfE {
    myString.substringWithRange(rangeOfE) // e
    myString[rangeOfE] // e

    // if you do want to create your own range
    // you can keep the index as a String.Index type
    let index = rangeOfE.startIndex
    myString.substringWithRange(Range<String.Index>(start: index, end: advance(index, 1))) // e

    // if you really really need the 
    // Int version of the index:
    let numericIndex = distance(index, advance(index, 1)) // 1 (type Int)
}

Jawaban terbaik bagi saya adalah func indexOf (target: String) -> Int {return (self as NSString) .rangeOfString (target) .lokasi}
YannSteph

2

Cara paling sederhana adalah:

Dalam Swift 3 :

 var textViewString:String = "HelloWorld2016"
    guard let index = textViewString.characters.index(of: "W") else { return }
    let mentionPosition = textViewString.distance(from: index, to: textViewString.endIndex)
    print(mentionPosition)

1

String adalah tipe jembatan untuk NSString, jadi tambahkan

import Cocoa

ke file cepat Anda dan gunakan semua metode "lama".


1

Dalam hal berpikir ini mungkin disebut INVESTASI. Anda menemukan bahwa dunia itu bulat, bukan datar. "Kamu tidak benar-benar perlu tahu INDEKS karakter untuk melakukan sesuatu dengannya." Dan sebagai seorang programmer C saya merasa sulit untuk mengambil juga! Baris Anda "biarkan indeks = letters.characters.indexOf (" c ")!" sudah cukup dengan sendirinya. Misalnya untuk menghapus huruf c yang bisa Anda gunakan ...

    var letters = "abcdefg"
  //let index = letters.rangeOfString("c")!.startIndex //is the same as
    let index = letters.characters.indexOf("c")!
    range = letters.characters.indexOf("c")!...letters.characters.indexOf("c")!
    letters.removeRange(range)
    letters

Namun, jika Anda menginginkan indeks, Anda perlu mengembalikan INDEX yang sebenarnya bukan Int karena nilai Int akan memerlukan langkah-langkah tambahan untuk penggunaan praktis apa pun. Ekstensi ini mengembalikan indeks, hitungan karakter tertentu, dan rentang yang akan ditunjukkan oleh kode plug-in-bermain ini.

extension String
{
    public func firstIndexOfCharacter(aCharacter: Character) -> String.CharacterView.Index? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                return index
            }

        }
        return nil
    }

    public func returnCountOfThisCharacterInString(aCharacter: Character) -> Int? {

        var count = 0
        for letters in self.characters{

            if aCharacter == letters{

                count++
            }
        }
        return count
    }


    public func rangeToCharacterFromStart(aCharacter: Character) -> Range<Index>? {

        for index in self.characters.indices {
            if self[index] == aCharacter {
                let range = self.startIndex...index
                return range
            }

        }
        return nil
    }

}



var MyLittleString = "MyVery:important String"

var theIndex = MyLittleString.firstIndexOfCharacter(":")

var countOfColons = MyLittleString.returnCountOfThisCharacterInString(":")

var theCharacterAtIndex:Character = MyLittleString[theIndex!]

var theRange = MyLittleString.rangeToCharacterFromStart(":")
MyLittleString.removeRange(theRange!)

1

Solusi Lengkap Swift 4:

OffsetIndexableCollection (String menggunakan Int Index)

https://github.com/frogcjn/OffsetIndexableCollection-String-Int-Indexable-

let a = "01234"

print(a[0]) // 0
print(a[0...4]) // 01234
print(a[...]) // 01234

print(a[..<2]) // 01
print(a[...2]) // 012
print(a[2...]) // 234
print(a[2...3]) // 23
print(a[2...2]) // 2

if let number = a.index(of: "1") {
    print(number) // 1
    print(a[number...]) // 1234
}

if let number = a.index(where: { $0 > "1" }) {
    print(number) // 2
}

1

ekstensi String {

//Fucntion to get the index of a particular string
func index(of target: String) -> Int? {
    if let range = self.range(of: target) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}
//Fucntion to get the last index of occurence of a given string
func lastIndex(of target: String) -> Int? {
    if let range = self.range(of: target, options: .backwards) {
        return characters.distance(from: startIndex, to: range.lowerBound)
    } else {
        return nil
    }
}

}


1

Cepat 5

Temukan indeks substring

let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
    let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
    print("index: ", index) //index: 2
}
else {
    print("substring not found")
}

Temukan indeks Karakter

let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
    let index: Int = str.distance(from: str.startIndex, to: firstIndex)
    print("index: ", index)   //index: 2
}
else {
    print("symbol not found")
}


0

Untuk mendapatkan indeks substring dalam string dengan Swift 2:

let text = "abc"
if let range = text.rangeOfString("b") {
   var index: Int = text.startIndex.distanceTo(range.startIndex) 
   ...
}

0

Dalam cepat 2.0

var stringMe="Something In this.World"
var needle="."
if let idx = stringMe.characters.indexOf(needle) {
    let pos=stringMe.substringFromIndex(idx)
    print("Found \(needle) at position \(pos)")
}
else {
    print("Not found")
}

0
let mystring:String = "indeep";
let findCharacter:Character = "d";

if (mystring.characters.contains(findCharacter))
{
    let position = mystring.characters.indexOf(findCharacter);
    NSLog("Position of c is \(mystring.startIndex.distanceTo(position!))")

}
else
{
    NSLog("Position of c is not found");
}

0

Saya bermain dengan mengikuti

extension String {
    func allCharactes() -> [Character] {
         var result: [Character] = []
         for c in self.characters {
             result.append(c)
         }
         return 
    }
}

sampai saya mengerti yang disediakan sekarang hanya array Karakter

dan dengan

let c = Array(str.characters)

Apa yang dicapai ini? Bagaimana blok kode pertama Anda lebih baik daripada yang kedua?
Ben Leggiero

0

Jika Anda hanya memerlukan indeks karakter, solusi paling cepat dan cepat (seperti yang telah ditunjukkan oleh Pascal) adalah:

let index = string.characters.index(of: ".")
let intIndex = string.distance(from: string.startIndex, to: index)

karakter disusutkan sekarang ... sayangnya, bahkan jika ini berfungsi
user3069232

0

Tentang subjek mengubah a String.Index menjadi Int, ekstensi ini berfungsi untuk saya:

public extension Int {
    /// Creates an `Int` from a given index in a given string
    ///
    /// - Parameters:
    ///    - index:  The index to convert to an `Int`
    ///    - string: The string from which `index` came
    init(_ index: String.Index, in string: String) {
        self.init(string.distance(from: string.startIndex, to: index))
    }
}

Contoh penggunaan yang relevan dengan pertanyaan ini:

var testString = "abcdefg"

Int(testString.range(of: "c")!.lowerBound, in: testString)     // 2

testString = "πŸ‡¨πŸ‡¦πŸ‡ΊπŸ‡ΈπŸ‡©πŸ‡ͺπŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦\u{1112}\u{1161}\u{11AB}"

Int(testString.range(of: "πŸ‡¨πŸ‡¦πŸ‡ΊπŸ‡ΈπŸ‡©πŸ‡ͺ")!.lowerBound, in: testString) // 0
Int(testString.range(of: "πŸ‘©β€πŸ‘©β€πŸ‘§β€πŸ‘¦")!.lowerBound, in: testString)    // 1
Int(testString.range(of: "ᄒᅑᆫ")!.lowerBound, in: testString)    // 5

Penting:

Seperti yang Anda tahu, itu mengelompokkan cluster grapheme diperpanjang dan bergabung karakter berbeda dari String.Index. Tentu saja, ini sebabnya kami punya String.Index. Anda harus ingat bahwa metode ini menganggap cluster sebagai karakter tunggal, yang lebih dekat untuk dikoreksi. Jika tujuan Anda adalah untuk membagi string dengan Unicode codepoint, ini bukan solusi untuk Anda.


0

Di Swift 2.0 , fungsi berikut mengembalikan substring sebelum karakter yang diberikan.

func substring(before sub: String) -> String {
    if let range = self.rangeOfString(sub),
        let index: Int = self.startIndex.distanceTo(range.startIndex) {
        return sub_range(0, index)
    }
    return ""
}

0

Anda dapat menemukan nomor indeks karakter dalam string dengan ini:

var str = "abcdefghi"
if let index = str.firstIndex(of: "c") {
    let distance = str.distance(from: str.startIndex, to: index)
    // distance is 2
}

0

Sebagai perspektif saya, Cara yang lebih baik dengan mengetahui logika itu sendiri ada di bawah

 let testStr: String = "I love my family if you Love us to tell us I'm with you"
 var newStr = ""
 let char:Character = "i"

 for value in testStr {
      if value == char {
         newStr = newStr + String(value)
   }

}
print(newStr.count)
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.