Trovare l'indice del carattere in Swift String


203

È tempo di ammettere la sconfitta ...

In Objective-C, potrei usare qualcosa come:

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

In Swift, vedo qualcosa di simile:

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

... ma questo mi dà solo un String.Index, che posso usare per abbonarmi nuovamente nella stringa originale, ma da cui non estrarre una posizione.

FWIW, che String.Indexha un ivar privato chiamato _positionche ha il valore corretto in esso. Solo non vedo come sia esposto.

So che potrei facilmente aggiungere questo a String me stesso. Sono più curioso di sapere cosa mi manca in questa nuova API.


Ecco un progetto GitHub che contiene molti metodi di estensione per la manipolazione delle stringhe Swift: github.com/iamjono/SwiftString
RenniePet

La migliore implementazione che ho trovato è qui: stackoverflow.com/a/32306142/4550651
Carlos García

È necessario distinguere tra punti di codifica Unicode e cluster di grapheme estesi?
Ben Leggiero,

Risposte:


248

Non sei l'unico che non è riuscito a trovare la soluzione.

Stringnon implementa RandomAccessIndexType. Probabilmente perché abilitano caratteri con diverse lunghezze di byte. Ecco perché dobbiamo usare string.characters.count( counto countElementsin Swift 1.x) per ottenere il numero di caratteri. Ciò vale anche per le posizioni. Il _positionè probabilmente un indice nella matrice di byte e non vogliono esporre questo. IlString.Index scopo è di proteggerci dall'accesso ai byte nel mezzo di caratteri.

Ciò significa che qualsiasi indice ottenuto deve essere creato da String.startIndexo String.endIndex( String.Indeximplementa BidirectionalIndexType). Qualsiasi altro indice può essere creato usando successoropredecessor metodi.

Ora per aiutarci con gli indici, c'è una serie di metodi (funzioni 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

Lavorare con String.Indexè ingombrante ma usare un wrapper per indicizzare per numeri interi (vedi https://stackoverflow.com/a/25152652/669586 ) è pericoloso perché nasconde l'inefficienza dell'indicizzazione reale.

Si noti che l'implementazione dell'indicizzazione Swift ha il problema che gli indici / intervalli creati per una stringa non possono essere utilizzati in modo affidabile per una stringa diversa , ad esempio:

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
Imbarazzante pensato che potesse essere, questa sembra essere la risposta. Si spera che queste due funzioni della gamma entrino nella documentazione prima della versione finale.
Matt Wilding,

Che tipo è rangeinvar range = text.rangeOfString("b")
zaph

5
@Zaph Ognuno Collectionha un typealias IndexType. Per le matrici, è definito come Int, poiché Stringè definito come String.Index. Sia le matrici che le stringhe possono anche usare gli intervalli (per creare sottostrati e sottostringhe). La gamma è di tipo speciale Range<T>. Per le stringhe, è Range<String.Index>per le matrici Range<Int>.
Sulthan,

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

1
@devios String, esattamente come NSStringin Foundation ha un metodo chiamato hasPrefix(_:).
Sulthan

86

Swift 3.0 rende questo un po 'più dettagliato:

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

Estensione:

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 questo è diventato più semplice:

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

Estensione:

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

Implementazione rapida 1.x:

Per una soluzione Swift pura è possibile utilizzare:

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

Come estensione a String:

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

1
i personaggi sono deprecati !!
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
Ho pensato di farlo, ma penso che sia un problema nascondere la semantica dell'accesso alle stringhe. Immagina di creare un'API per accedere agli elenchi collegati che assomiglia all'API per un array. Le persone vorrebbero scrivere un codice orribilmente inefficiente.
Erik Engheim,

16

Ho trovato questa soluzione per swift2:

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

quale sarà l'indice quando 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        
  }
}

indice di ritorno (di: elemento) .map {target.distance (da: startIndex, a: $ 0)}
frogcjn

8

Non sono sicuro di come estrarre la posizione da String.Index, ma se si è disposti a ripiegare su alcuni framework Objective-C, è possibile eseguire il bridge su object-c e farlo come una volta.

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

Sembra che alcuni metodi NSString non siano ancora stati (o forse non saranno) portati su String. Contiene anche viene in mente.


In realtà sembra che l'accesso alla proprietà location del valore restituito sia sufficiente affinché il compilatore deduca un tipo NSString, quindi la bridgeToObjectiveC()chiamata non è necessaria. Il mio problema sembra manifestarsi solo quando si chiama rangeOfStringSwift String già esistente. Sembra un problema API ...
Matt Wilding

Interessante. Non sapevo che fosse dedotto in quei casi. Bene, quando è già una stringa puoi sempre usare il bridge.
Connor

8

Ecco un'estensione String pulita che risponde alla domanda:

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

Puoi anche trovare gli indici di un carattere in una singola stringa come questa,

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

}

Che dà il risultato in [String.Distance] ie. [Int], come

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

5

Se si desidera utilizzare NSString familiare, è possibile dichiararlo esplicitamente:

var someString: NSString = "abcdefghi"

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

Non sono ancora sicuro di come farlo in Swift.


1
Questo sicuramente funziona e sembra che il compilatore sia piuttosto aggressivo nell'investire i tipi NSString per te. Speravo davvero in un modo Swift puro di farlo, dal momento che sembra un caso d'uso abbastanza comune.
Matt Wilding,

Sì, mi guardo intorno, ma non lo vedo. È possibile che si siano concentrati su aree non supportate da ObjC perché possono colmare queste lacune senza troppe capacità perse. Sto solo pensando ad alta voce :)
Logan,

4

Se vuoi conoscere la posizione di un carattere in una stringa come valore int , usa questo:

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

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

3

So che questo è vecchio e una risposta è stata accettata, ma puoi trovare l'indice della stringa in un paio di righe di codice usando:

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

Qualche altra grande informazione sulle stringhe Swift qui Stringhe in Swift


findnon è disponibile in Swift 3
AamirR il

2

Questo ha funzionato per me,

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

anche questo ha funzionato,

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

Entrambi sembrano in qualche modo ricadere sul comportamento di NSString. Nel mio parco giochi, stavo usando una variabile intermedia per la stringa. var str = "abcdef"; str.rangeOfString("c").locationgenera un errore su String.Index che non ha un membro chiamato location ...
Matt Wilding

1
Funziona solo perché "string" è NSString e non Swift's String
gderaco

2

Il tipo di variabile String in Swift contiene funzioni diverse rispetto a NSString in Objective-C. E come menzionato Sulthan,

Swift String non implementa RandomAccessIndex

Quello che puoi fare è eseguire il downcast della tua variabile di tipo String su NSString (questo è valido in Swift). Questo ti darà accesso alle funzioni di NSString.

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

2

Se ci pensi, in realtà non hai davvero bisogno della versione Int esatta della posizione. L'intervallo o anche String.Index è sufficiente per ottenere nuovamente la sottostringa, se necessario:

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

La risposta migliore per me è che func indexOf (target: String) -> Int {return (self as NSString) .rangeOfString (target) .location}
YannSteph

2

Il modo più semplice è:

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 è un tipo di bridge per NSString, quindi aggiungi

import Cocoa

al tuo file rapido e usa tutti i metodi "vecchi".


1

In termini di pensiero questo potrebbe essere chiamato INVERSIONE. Scopri che il mondo è rotondo anziché piatto. "Non hai davvero bisogno di conoscere l'INDICE del personaggio per farci qualcosa." E come programmatore C ho trovato anche quello difficile da prendere! La tua riga "let index = letters.characters.indexOf (" c ")!" è abbastanza da solo. Ad esempio per rimuovere la c potresti usare ... (incolla il parco giochi)

    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

Tuttavia, se si desidera un indice, è necessario restituire un INDICE effettivo, non un Int, poiché un valore Int richiederebbe passaggi aggiuntivi per qualsiasi uso pratico. Queste estensioni restituiscono un indice, un conteggio di un carattere specifico e un intervallo che dimostrerà questo codice plug-in in grado di giocare.

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 Soluzione completa:

OffsetIndexableCollection (String utilizzando 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

extension 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

Swift 5

Trova indice di sottostringa

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

Trova indice di carattere

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

Per ottenere l'indice di una sottostringa in una stringa con Swift 2:

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

0

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

Io gioco con il seguente

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

finché non capisco quello fornito ora è solo l'array di caratteri

e con

let c = Array(str.characters)

Cosa compie questo? In che modo il tuo primo blocco di codice è migliore del secondo?
Ben Leggiero,

0

Se hai solo bisogno dell'indice di un personaggio, la soluzione più semplice e veloce (come già sottolineato da Pascal) è:

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

i personaggi ora sono ammortizzati ... purtroppo, anche se funziona
user3069232

0

Per quanto riguarda la trasformazione di a String.Indexin Int, questa estensione funziona per me:

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

Esempio di utilizzo rilevante per questa domanda:

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

Importante:

Come puoi vedere, raggruppa gruppi di grafi estesi e unisce i personaggi in modo diverso String.Index. Naturalmente, questo è il motivo per cui abbiamo String.Index. È necessario tenere presente che questo metodo considera i cluster come caratteri singolari, che è più vicino alla correzione. Se il tuo obiettivo è dividere una stringa per punto di codice Unicode, questa non è la soluzione per te.


0

In Swift 2.0 , la seguente funzione restituisce una sottostringa prima di un determinato carattere.

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

Puoi trovare il numero indice di un carattere in una stringa con questo:

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

0

Come la mia prospettiva, il modo migliore per conoscere la logica stessa è di seguito

 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)
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.