Index des Zeichens in Swift String finden


203

Es ist Zeit, eine Niederlage zuzugeben ...

In Objective-C könnte ich Folgendes verwenden:

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

In Swift sehe ich etwas Ähnliches:

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

... aber das gibt mir nur eine String.Index, mit der ich die ursprüngliche Zeichenfolge zurückschreiben, aber keinen Ort daraus extrahieren kann.

FWIW, das String.Indexhat einen privaten ivar namens _position, der den richtigen Wert enthält. Ich sehe nur nicht, wie es ausgesetzt ist.

Ich weiß, dass ich dies leicht selbst zu String hinzufügen könnte. Ich bin neugieriger auf das, was mir in dieser neuen API fehlt.


Hier ist ein GitHub-Projekt, das viele Erweiterungsmethoden für die
schnelle

Die beste Implementierung, die ich gefunden habe, ist hier: stackoverflow.com/a/32306142/4550651
Carlos García

Müssen Sie zwischen Unicode-Codepunkten und erweiterten Graphemclustern unterscheiden?
Ben Leggiero

Antworten:


248

Sie sind nicht der einzige, der die Lösung nicht finden konnte.

Stringnicht implementiert RandomAccessIndexType. Wahrscheinlich, weil sie Zeichen mit unterschiedlichen Bytelängen aktivieren. Deshalb müssen wir string.characters.count( countoder countElementsin Swift 1.x) verwenden, um die Anzahl der Zeichen zu erhalten. Das gilt auch für Positionen. Das _positionist wahrscheinlich ein Index in das rohe Array von Bytes und sie wollen das nicht offenlegen. Das String.Indexsoll uns vor dem Zugriff auf Bytes in der Mitte von Zeichen schützen.

Das bedeutet, dass jeder Index, den Sie erhalten, aus String.startIndexoder String.endIndex( String.Indeximplementiert BidirectionalIndexType) erstellt werden muss. Alle anderen Indizes können mit successoroder predecessorMethoden erstellt werden .

Um uns bei den Indizes zu helfen, gibt es eine Reihe von Methoden (Funktionen in 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

Das Arbeiten mit String.Indexist umständlich, aber die Verwendung eines Wrappers zum Indizieren nach Ganzzahlen (siehe https://stackoverflow.com/a/25152652/669586 ) ist gefährlich, da dadurch die Ineffizienz einer echten Indizierung verborgen wird.

Beachten Sie, dass die Swift-Indizierungsimplementierung das Problem hat, dass für eine Zeichenfolge erstellte Indizes / Bereiche nicht zuverlässig für eine andere Zeichenfolge verwendet werden können. Beispiel:

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
Ich dachte, es könnte die Antwort sein. Hoffentlich schaffen es diese beiden Bereichsfunktionen irgendwann vor der endgültigen Veröffentlichung in die Dokumentation.
Matt Wilding

Welcher Typ ist rangeinvar range = text.rangeOfString("b")
zaph

5
@ Zaph Jeder Collectionhat eine typealias IndexType. Für Arrays ist es definiert als Int, denn Stringes ist definiert als String.Index. Sowohl Arrays als auch Strings können auch Bereiche verwenden (um Subarrays und Teilzeichenfolgen zu erstellen). Das Sortiment ist ein besonderer Typ Range<T>. Für Strings ist es Range<String.Index>für Arrays Range<Int>.
Sulthan

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

1
@devios Stringhat genau wie NSStringin Foundation eine Methode namens hasPrefix(_:).
Sulthan

86

Swift 3.0 macht dies etwas ausführlicher:

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

Erweiterung:

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

In Swift 2.0 ist dies einfacher geworden:

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

Erweiterung:

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

Schnelle 1.x-Implementierung:

Für eine reine Swift-Lösung kann man verwenden:

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

Als Erweiterung zu String:

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

1
Zeichen ist veraltet !!
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
Ich habe darüber nachgedacht, aber ich denke, es ist ein Problem, dass es die Semantik des String-Zugriffs verbirgt. Stellen Sie sich vor, Sie erstellen eine API für den Zugriff auf verknüpfte Listen, die genau wie die API für ein Array aussieht. Die Leute möchten schrecklich ineffizienten Code schreiben.
Erik Engheim

16

Ich habe diese Lösung für swift2 gefunden:

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

Was wird Index sein, wenn str = "abcdefgchi"
Vatsal Shukla

10

Swift 5.0

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

Swift 4.0

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

Rückgabeindex (von: Element) .map {target.distance (von: startIndex bis: $ 0)}
frogcjn

8

Ich bin nicht sicher, wie ich die Position aus String.Index extrahieren soll, aber wenn Sie bereit sind, auf einige Objective-C-Frameworks zurückzugreifen, können Sie eine Brücke zu Objective-C schlagen und dies auf die gleiche Weise tun, wie Sie es früher getan haben.

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

Es scheint, dass einige NSString-Methoden noch nicht auf String portiert wurden (oder werden). Enthält auch in den Sinn.


Es scheint tatsächlich, dass der Zugriff auf die location-Eigenschaft des Rückgabewerts ausreicht, damit der Compiler auf einen NSString-Typ schließen kann, sodass der bridgeToObjectiveC()Aufruf nicht benötigt wird. Mein Problem scheint sich nur beim Aufrufen eines rangeOfStringzuvor vorhandenen Swift-Strings zu manifestieren . Scheint wie ein API-Problem ...
Matt Wilding

das ist interessant. Ich wusste nicht, dass es in diesen Fällen abgeleitet wurde. Nun, wenn es bereits ein String ist, können Sie immer die Brücke verwenden.
Connor

8

Hier ist eine saubere String-Erweiterung, die die Frage beantwortet:

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

Sie können auch Indizes eines Zeichens in einer einzelnen Zeichenfolge wie folgt finden:

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

}

Was das Ergebnis in [String.Distance] ergibt, dh. [Int], wie

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

5

Wenn Sie vertrautes NSString verwenden möchten, können Sie es explizit deklarieren:

var someString: NSString = "abcdefghi"

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

Ich bin mir noch nicht sicher, wie ich das in Swift machen soll.


1
Dies funktioniert sicherlich und es scheint, dass der Compiler ziemlich aggressiv darin ist, NSString-Typen für Sie abzuleiten. Ich hatte wirklich auf eine reine Swift-Methode gehofft, da dies ein ausreichend häufiger Anwendungsfall zu sein scheint.
Matt Wilding

Ja, ich sehe mich um, aber ich sehe es nicht. Möglicherweise haben sie sich auf Bereiche konzentriert, die von ObjC nicht unterstützt werden, da sie diese Lücken füllen können, ohne dass zu viele Funktionen verloren gehen. Ich denke nur laut nach :)
Logan

4

Wenn Sie die Position eines Zeichens in einer Zeichenfolge als int- Wert kennen möchten, verwenden Sie Folgendes :

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

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

3

Ich weiß, dass dies alt ist und eine Antwort akzeptiert wurde, aber Sie können den Index der Zeichenfolge in ein paar Codezeilen finden, indem Sie:

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

Einige andere großartige Informationen zu Swift-Strings hier Strings in Swift


findist nicht verfügbar in Swift 3
AamirR

2

Das hat bei mir funktioniert,

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

das hat auch funktioniert,

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

Beide scheinen irgendwie auf das Verhalten von NSString zurückzugreifen. Auf meinem Spielplatz habe ich eine Zwischenvariable für die Saite verwendet. var str = "abcdef"; str.rangeOfString("c").locationlöst einen Fehler über String.Index aus, der kein Mitglied mit dem Namen location hat ...
Matt Wilding

1
Es funktioniert nur, weil "string" NSString ist und nicht Swifts String
gderaco

2

Der Variablentyp String in Swift enthält andere Funktionen als NSString in Objective-C. Und wie Sulthan erwähnte,

Swift String implementiert RandomAccessIndex nicht

Was Sie tun können, ist, Ihre Variable vom Typ String auf NSString herunterzuspielen (dies gilt in Swift). Dadurch erhalten Sie Zugriff auf die Funktionen in NSString.

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

2

Wenn Sie darüber nachdenken, benötigen Sie die genaue Int-Version des Speicherorts nicht wirklich. Der Bereich oder sogar der String.Index reicht aus, um den Teilstring bei Bedarf wieder herauszuholen:

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

Die beste Antwort für mich ist, dass func indexOf (Ziel: String) -> Int {return (selbst als NSString) .rangeOfString (Ziel) .location}
YannSteph

2

Der einfachste Weg ist:

In 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 ist ein Brückentyp für NSString

import Cocoa

in Ihre schnelle Datei und verwenden Sie alle "alten" Methoden.


1

In Bezug auf das Denken könnte dies eine INVERSION genannt werden. Sie entdecken, dass die Welt rund statt flach ist. "Du musst den INDEX des Charakters nicht wirklich kennen, um Dinge damit zu machen." Und als C-Programmierer fand ich das auch schwer zu ertragen! Ihre Zeile "let index = letter.characters.indexOf (" c ")!" ist genug für sich. Zum Beispiel, um das c zu entfernen, könnten Sie verwenden ... (Spielplatz einfügen)

    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

Wenn Sie jedoch einen Index möchten, müssen Sie einen tatsächlichen INDEX zurückgeben, nicht einen Int, da ein Int-Wert zusätzliche Schritte für jede praktische Verwendung erfordern würde. Diese Erweiterungen geben einen Index, eine Anzahl eines bestimmten Zeichens und einen Bereich zurück, den dieser Plug-in-fähige Spielplatzcode demonstriert.

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

Swift 4 Komplettlösung:

OffsetIndexableCollection (Zeichenfolge mit 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

Erweiterungszeichenfolge {

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

Swift 5

Index der Teilzeichenfolge suchen

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

Index des Charakters finden

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

So erhalten Sie mit Swift 2 den Index eines Teilstrings in einem String:

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

0

In schnell 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

Ich spiele mit folgendem

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

Bis ich das bereitgestellte verstehe, ist es nur ein Zeichenarray

und mit

let c = Array(str.characters)

Was bringt das? Wie ist Ihr erster Codeblock besser als der zweite?
Ben Leggiero

0

Wenn Sie nur den Index eines Zeichens benötigen, ist die einfachste und schnellste Lösung (wie bereits von Pascal ausgeführt):

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

Zeichen wird jetzt abgeschrieben ... leider, auch wenn dies funktioniert
user3069232

0

In Bezug auf das Verwandeln von a String.Indexin a Intfunktioniert diese Erweiterung für mich:

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

Beispiel für eine Verwendung, die für diese Frage relevant ist:

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

Wichtig:

Wie Sie sehen können, werden erweiterte Graphemcluster und verbundene Zeichen anders gruppiert als String.Index. Das ist natürlich der Grund, warum wir haben String.Index. Beachten Sie, dass bei dieser Methode Cluster als einzelne Zeichen betrachtet werden, was näher an der Korrektur liegt. Wenn Sie eine Zeichenfolge nach Unicode-Codepunkt aufteilen möchten, ist dies nicht die Lösung für Sie.


0

In Swift 2.0 gibt die folgende Funktion einen Teilstring vor einem bestimmten Zeichen zurück.

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

Sie können die Indexnummer eines Zeichens in einer Zeichenfolge folgendermaßen finden:

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

0

Aus meiner Sicht ist der bessere Weg, die Logik selbst zu kennen, unten

 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)
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.