Ottieni l'ennesimo carattere di una stringa nel linguaggio di programmazione Swift


419

Come posso ottenere l'ennesimo carattere di una stringa? Ho provato []accessor parentesi ( ) senza fortuna.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ERRORE: 'subscript' non è disponibile: impossibile sottoscrivere String con un Int, vedere il commento della documentazione per la discussione


1
Il messaggio di errore "impossibile sottoscrivere String con un Int, vedere il commento della documentazione per la discussione" sembra riferirsi a github.com/apple/swift/blob/master/stdlib/public/core/…
andrewdotn

usa var firstChar = string.index(string.startIndex, offsetBy: 0)invece
Sazzad Hissain Khan

@SazzadHissainKhan questo comporterebbe un indice di stringa, non un carattere. A proposito, perché non semplicemente string.startIndex? Per il primo personaggio string[string.startIndex] o semplicemente string.first. Si noti che il primo approccio che si dovrebbe verificare se la stringa è vuota prima il secondo restituisce un facoltativo
Leo Dabus

Risposte:


567

Attenzione: consultare la risposta di Leo Dabus per una corretta implementazione di Swift 4 e Swift 5.

Swift 4 o successivo

Il Substringtipo è stato introdotto in Swift 4 per rendere le sottostringhe più veloci ed efficienti condividendo la memoria con la stringa originale, quindi è ciò che dovrebbero restituire le funzioni dei pedici.

Provalo qui

extension StringProtocol {
    subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
    subscript(range: Range<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
        return self[startIndex..<index(startIndex, offsetBy: range.count)]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence { self[..<index(startIndex, offsetBy: range.upperBound)] }
}

Per convertire Substringin a String, puoi semplicemente farlo String(string[0..2]), ma dovresti farlo solo se prevedi di mantenere la sottostringa in giro. Altrimenti, è più efficiente mantenerlo a Substring.

Sarebbe bello se qualcuno potesse trovare un buon modo per unire queste due estensioni in una sola. Ho provato ad estendere StringProtocol senza successo, perché il indexmetodo non esiste lì. Nota: questa risposta è già stata modificata, è implementata correttamente e ora funziona anche per sottostringhe. Assicurati di utilizzare un intervallo valido per evitare arresti anomali durante la sottoscrizione del tipo StringProtocol. Per la sottoscrizione con un intervallo che non si arresta in modo anomalo con valori non compresi nell'intervallo, è possibile utilizzare questa implementazione


Perché questo non è integrato?

Il messaggio di errore dice "vedere il commento della documentazione per la discussione" . Apple fornisce la seguente spiegazione nel file UnavailableStringAPIs.swift :

La sottoscrizione di stringhe con numeri interi non è disponibile.

Il concetto di "il ith carattere in una stringa" ha interpretazioni diverse in diverse librerie e componenti di sistema. L'interpretazione corretta deve essere selezionata in base al caso d'uso e alle API interessate, quindi String non può essere sottoscritta con un numero intero.

Swift offre diversi modi per accedere ai dati dei caratteri memorizzati all'interno delle stringhe.

  • String.utf8è una raccolta di unità di codice UTF-8 nella stringa. Utilizzare questa API per convertire la stringa in UTF-8. La maggior parte delle API POSIX elabora le stringhe in termini di unità di codice UTF-8.

  • String.utf16è una raccolta di unità di codice UTF-16 in stringa. La maggior parte delle API touch Cocoa e Cocoa elabora le stringhe in termini di unità di codice UTF-16. Ad esempio, le istanze NSRangeutilizzate con NSAttributedStringe NSRegularExpressionmemorizzano gli offset e le lunghezze della sottostringa in termini di unità di codice UTF-16.

  • String.unicodeScalarsè una raccolta di scalari Unicode. Utilizzare questa API quando si eseguono manipolazioni di basso livello dei dati dei personaggi.

  • String.characters è una raccolta di cluster grapheme estesi, che sono un'approssimazione di caratteri percepiti dall'utente.

Si noti che quando si elaborano stringhe che contengono testo leggibile dall'uomo, l'elaborazione per carattere dovrebbe essere evitata nella massima misura possibile. Uso alto livello di localizzazione sensibili Unicode algoritmi invece, per esempio, String.localizedStandardCompare(), String.localizedLowercaseString, String.localizedStandardRangeOfString()etc.


4
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.Indexe Intnon sono compatibili.

7
Se vedete Cannot subscript a value of type 'String'...controllare questa risposta: stackoverflow.com/a/31265316/649379
SoftDesigner

24
Quando provo ad usare questo, ottengo Ambiguous use of 'subscript'.
jowie

18
AVVERTIMENTO! L'estensione di seguito è terribilmente inefficiente. Ogni volta che si accede a una stringa con un numero intero, viene eseguita una funzione O (n) per far avanzare il suo indice iniziale. L'esecuzione di un loop lineare all'interno di un altro loop lineare significa che questo per loop è accidentalmente O (n2) - all'aumentare della lunghezza della stringa, il tempo impiegato da questo loop aumenta in modo quadratico. Invece di farlo, potresti usare la raccolta di stringhe dei personaggi.
ignaciohugog,

2
fatal error: Can't form a Character from an empty String
Devin B,

341

Swift 5.2

let str = "abcdef"
str[1 ..< 3] // returns "bc"
str[5] // returns "f"
str[80] // returns ""
str.substring(fromIndex: 3) // returns "def"
str.substring(toIndex: str.length - 2) // returns "abcd"

Dovrai aggiungere questa estensione String al tuo progetto (è completamente testato):

extension String {

    var length: Int {
        return count
    }

    subscript (i: Int) -> String {
        return self[i ..< i + 1]
    }

    func substring(fromIndex: Int) -> String {
        return self[min(fromIndex, length) ..< length]
    }

    func substring(toIndex: Int) -> String {
        return self[0 ..< max(0, toIndex)]
    }

    subscript (r: Range<Int>) -> String {
        let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
                                            upper: min(length, max(0, r.upperBound))))
        let start = index(startIndex, offsetBy: range.lowerBound)
        let end = index(start, offsetBy: range.upperBound - range.lowerBound)
        return String(self[start ..< end])
    }
}

Anche se Swift ha sempre avuto una soluzione pronta all'uso a questo problema (senza estensione String, che ho fornito di seguito), consiglierei vivamente di utilizzare l'estensione. Perché? Perché mi ha salvato decine di ore di dolorose migrazioni dalle prime versioni di Swift, in cui la sintassi di String stava cambiando quasi ogni versione, ma tutto quello che dovevo fare era aggiornare l'implementazione dell'estensione invece di refactoring dell'intero progetto. Fate la vostra scelta.

let str = "Hello, world!"
let index = str.index(str.startIndex, offsetBy: 4)
str[index] // returns Character 'o'

let endIndex = str.index(str.endIndex, offsetBy:-2)
str[index ..< endIndex] // returns String "o, worl"

String(str.suffix(from: index)) // returns String "o, world!"
String(str.prefix(upTo: index)) // returns String "Hell"

cambia range.upperBound - range.lowerBoundinrange.count
Leo Dabus il

Non fa parte della domanda originale, ma ... sarebbe bello se anche questo compito supportato. Ad esempio, s [i] = "a" :).
Chris Prince,

2
Credo che con gli abbonamenti Swift 4.2 non siano di nuovo disponibili. Viene visualizzato un errore che dice: "pedice" non disponibile: impossibile sottoscrivere una stringa con un Int, vedere il commento della documentazione per la discussione
C0D3

2
@ChrisPrinceextension StringProtocol where Self: RangeReplaceableCollection { subscript(offset: Int) -> Element { get { return self[index(startIndex, offsetBy: offset)] } set { let start = index(startIndex, offsetBy: offset) replaceSubrange(start..<index(after: start), with: [newValue]) } } }
Leo Dabus,

Dovrebbero essere funzioni integrate
Ammar Mujeeb,

150

Ho appena escogitato questa soluzione alternativa

var firstChar = Array(string)[0]

2
Questa è una buona soluzione rapida per il caso (comune) in cui sai di avere stringhe codificate UTF8 o ASCII. Basta essere sicuri che le stringhe non saranno mai in una codifica che utilizza più di un byte.
Jeff Hay,

48
Sembra estremamente inefficiente poiché stai copiando l'intera stringa solo per ottenere il primo carattere. Usa stringa [stringa. startIndex] invece di 0 come ha sottolineato Sulthan.
Bjorn,

6
Scartare la stringa: var firstChar = Array(string!)[0]altrimenti diràadd arrayLiteral
Mohammad Zaid Pathan,

2
Non credo che questo sia pulito, in realtà è un giro. Non sono del tutto sicuro di quale inizializzatore nell'array venga utilizzato per primo perché ciò accada (e presumo che sia l'inizializzatore SequenceType che causa la raccolta dei caratteri della stringa come singoli componenti dell'array). Questo non è affatto esplicito e potrebbe essere corretto in futuro senza il cast del tipo. Questo non funziona anche se si utilizza la scorciatoia per l'array tramite [string] .first. La soluzione di @ Sulthan funziona meglio per utilizzare i valori di indice al forno. È molto più chiaro cosa sta succedendo qui.
TheCodingArt

2
Caspita, errore del segmento del compilatore!
Frank,

124

Nessuna indicizzazione usando numeri interi, solo usando String.Index. Principalmente con complessità lineare. È inoltre possibile creare intervalli da String.Indexe ottenere sottostringhe utilizzandoli.

Swift 3.0

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.index(before: someString.endIndex)]
let charAtIndex = someString[someString.index(someString.startIndex, offsetBy: 10)]

let range = someString.startIndex..<someString.index(someString.startIndex, offsetBy: 10)
let substring = someString[range]

Swift 2.x

let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.endIndex.predecessor()]
let charAtIndex = someString[someString.startIndex.advanceBy(10)]

let range = someString.startIndex..<someString.startIndex.advanceBy(10)
let subtring = someString[range]

Nota che non puoi mai usare un indice (o intervallo) creato da una stringa a un'altra stringa

let index10 = someString.startIndex.advanceBy(10)

//will compile
//sometimes it will work but sometimes it will crash or result in undefined behaviour
let charFromAnotherString = anotherString[index10]

7
Stringgli indici sono unici per una stringa. Questo perché stringhe diverse possono avere UTF-16 multi-unità diverse Characterse / o in posizioni diverse in modo che gli indici delle unità UTF-16 non corrispondano, potrebbero cadere oltre la fine o il punto all'interno di una UTF-16 multi-unità Character.
zaph,

@Zaph Questo è ovvio.
Sulthan,

3
Spiegando perché dici: "a volte si arresta in modo anomalo o provoca un comportamento indefinito". Forse meglio dire semplicemente non farlo perché ...
zaph

1
@Sulthan ..è ora ..<(nel tuo incarico di range)
Aaron Brager il

3
@CajunLuke So che è passato un po 'di tempo da quando hai pubblicato questo commento, ma dai un'occhiata a questa risposta . Puoi usarevar lastChar = string[string.endIndex.predecessor()]
David L

122

Xcode 11 • Swift 5.1

È possibile estendere StringProtocol per rendere disponibile il pedice anche alle sottostringhe:

extension StringProtocol {
    subscript(_ offset: Int)                     -> Element     { self[index(startIndex, offsetBy: offset)] }
    subscript(_ range: Range<Int>)               -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: ClosedRange<Int>)         -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
    subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
    subscript(_ range: PartialRangeUpTo<Int>)    -> SubSequence { prefix(range.upperBound) }
    subscript(_ range: PartialRangeFrom<Int>)    -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}

extension LosslessStringConvertible {
    var string: String { .init(self) }
}

extension BidirectionalCollection {
    subscript(safe offset: Int) -> Element? {
        guard !isEmpty, let i = index(startIndex, offsetBy: offset, limitedBy: index(before: endIndex)) else { return nil }
        return self[i]
    }
}

analisi

let test = "Hello USA 🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[safe: 10]   // "🇺🇸"
test[11]   // "!"
test[10...]   // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[10..<12]   // "🇺🇸!"
test[10...12]   // "🇺🇸!!"
test[...10]   // "Hello USA 🇺🇸"
test[..<10]   // "Hello USA "
test.first   // "H"
test.last    // "!"

// Subscripting the Substring
 test[...][...3]  // "Hell"

// Note that they all return a Substring of the original String.
// To create a new String from a substring
test[10...].string  // "🇺🇸!!! Hello Brazil 🇧🇷!!!"

Posso chiedere che cos'è "self [index (startIndex, offsetBy: i)]"? E come funziona "self [i]"?
Allenlinli,

1
Ciao Leo, grazie per la soluzione! Sono passato (oggi) da Swift 2.3 a 3 e il tuo indice della soluzione (range: Range <Int>) riporta l'errore "Argomento extra 'limitedBy' in call". Cosa pensi possa essere sbagliato?
Ahmet Akkök,

@ AhmetAkkök sei sicuro di non aver cambiato il codice?
Leo Dabus,

1
@Leo si è scoperto che non ho convertito l'intero progetto ma sull'app non l'estensione, ho ripetuto il processo sia per l'app che per l'estensione e ora funziona bene. Il tuo aiuto è molto apprezzato!
Ahmet Akkök,

questo è un codice molto complicato. Qual è il vantaggio rispetto a fare return String(Array(characters)[range])in Swift 3?
Dan Rosenstark,

69

Swift 4

let str = "My String"

Stringa all'indice

let index = str.index(str.startIndex, offsetBy: 3)
String(str[index])    // "S"

substring

let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
String(str[startIndex...endIndex])     // "Strin"

Primi n caratteri

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[..<startIndex])    // "My "

Ultimi n caratteri

let startIndex = str.index(str.startIndex, offsetBy: 3)
String(str[startIndex...])    // "String"

Swift 2 e 3

str = "My String"

** Stringa all'indice **

Swift 2

let charAtIndex = String(str[str.startIndex.advancedBy(3)])  // charAtIndex = "S"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)]

Sottostringa da Index a Index

Swift 2

let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)] // subStr = "Strin"

Swift 3

str[str.index(str.startIndex, offsetBy: 3)...str.index(str.startIndex, offsetBy: 7)]

Primi n caratteri

let first2Chars = String(str.characters.prefix(2)) // first2Chars = "My"

Ultimi n caratteri

let last3Chars = String(str.characters.suffix(3)) // last3Chars = "ing"

24

Swift 2.0 a partire da Xcode 7 GM Seed

var text = "Hello, world!"

let firstChar = text[text.startIndex.advancedBy(0)] // "H"

Per l'ennesimo carattere, sostituire 0 con n-1.

Modifica: Swift 3.0

text[text.index(text.startIndex, offsetBy: 0)]


nb ci sono modi più semplici per afferrare alcuni caratteri nella stringa

per esempio let firstChar = text.characters.first


23

Se vedi Cannot subscript a value of type 'String'...usare questa estensione:

Swift 3

extension String {
    subscript (i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    subscript (i: Int) -> String {
        return String(self[i] as Character)
    }

    subscript (r: Range<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start..<end]
    }

    subscript (r: ClosedRange<Int>) -> String {
        let start = index(startIndex, offsetBy: r.lowerBound)
        let end = index(startIndex, offsetBy: r.upperBound)
        return self[start...end]
    }
}

Swift 2.3

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = advance(startIndex, integerIndex)
        return self[index]
    }

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

Fonte: http://oleb.net/blog/2014/07/swift-strings/


19

Soluzione Swift 2.2:

La seguente estensione funziona in Xcode 7, questa è una combinazione di questa soluzione e della conversione della sintassi Swift 2.0.

extension String {
    subscript(integerIndex: Int) -> Character {
        let index = startIndex.advancedBy(integerIndex)
        return self[index]
    }

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

14

La classe di stringhe rapide non offre la possibilità di ottenere un carattere in un indice specifico a causa del suo supporto nativo per i caratteri UTF. La lunghezza variabile di un personaggio UTF in memoria rende impossibile saltare direttamente a un personaggio. Ciò significa che devi ripetere manualmente il ciclo sulla stringa ogni volta.

È possibile estendere String per fornire un metodo che eseguirà il ciclo tra i caratteri fino all'indice desiderato

extension String {
    func characterAtIndex(index: Int) -> Character? {
        var cur = 0
        for char in self {
            if cur == index {
                return char
            }
            cur++
        }
        return nil
    }
}

myString.characterAtIndex(0)!

3
Puoi già scorrere tra le stringhe: per la lettera in "pippo" {println (lettera)}
Doobeh

@Doobeh intendevo passare in rassegna e restituire il personaggio reale come nella mia modifica sopra
drewag

simpatico! È divertente come puoi iterarlo, ma non tramite un indice. Sensazione di pitone di Swift ma con bordi più duri.
Doobeh

Ho scoperto che usando myString.bridgeToObjectiveC().characterAtIndex(0)o (string as NSString ).characterAtIndex(0) restituisce un valore Int del personaggio
markhunte

4
Non usare NSStringmetodi per accedere a singoli caratteri da un nativo di Swift String: i due usano diversi meccanismi di conteggio, quindi otterrai risultati imprevedibili con caratteri Unicode più alti. Il primo metodo dovrebbe essere sicuro (una volta gestiti i bug Unicode di Swift).
Nate Cook,

11

Come nota a parte, ci sono alcune funzioni applicabili direttamente alla rappresentazione della catena di caratteri di una stringa, in questo modo:

var string = "Hello, playground"
let firstCharacter = string.characters.first // returns "H"
let lastCharacter = string.characters.last // returns "d"

Il risultato è di tipo Carattere, ma puoi lanciarlo su una stringa.

O questo:

let reversedString = String(string.characters.reverse())
// returns "dnuorgyalp ,olleH" 

:-)


11

Swift 4

String(Array(stringToIndex)[index]) 

Questo è probabilmente il modo migliore per risolvere questo problema una volta. Probabilmente si desidera eseguire prima il cast della stringa come matrice, quindi di nuovo il risultato come stringa. In caso contrario, verrà restituito un carattere anziché una stringa.

L'esempio String(Array("HelloThere")[1])restituirà "e" come stringa.

(Array("HelloThere")[1] restituirà "e" come personaggio.

Swift non consente alle stringhe di essere indicizzate come matrici, ma questo porta a termine il lavoro, in stile forza bruta.


1
La duplicazione del contenuto dell'intera stringa in un'altra posizione di memoria è contro-performante, specialmente per stringhe di grandi dimensioni. Non dovremmo aver bisogno di allocazioni di memoria extra per compiti semplici come l'accesso diretto alla memoria.
Cristik,

7

La mia soluzione molto semplice:

Swift 4.1:

let myString = "Test string"
let index = 0
let firstCharacter = myString[String.Index(encodedOffset: index)]

Swift 5.1:

let firstCharacter = myString[String.Index.init(utf16Offset: index, in: myString)]

Funziona in Swift 4.1
leanne

Soluzione più semplice e ora con l'esempio di Swift 5 :)
OhadM,

@Linh Dao Non usare encodedOffset. encodedOffset è obsoleto: encodedOffset è stato deprecato poiché l'utilizzo più comune non è corretto.
Leo Dabus,

@OhadM più semplice non significa che sia corretto o almeno non funzionerà come previsto Trylet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Leo Dabus

1
@OhadM Il mio commento è stato solo un avvertimento. Sentiti libero di usarlo se pensi che si comporti come ti aspetti.
Leo Dabus,

6

Puoi farlo convertendo String in Array e ottenerlo per indice specifico usando il pedice come di seguito

var str = "Hello"
let s = Array(str)[2]
print(s)

1
Si noti che questa soluzione comporterà la duplicazione dei contenuti, rendendola meno performante in termini di memoria e CPU.
Cristik,

5

Ho appena avuto lo stesso problema. Basta fare questo:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)

Questo fallisce per molti Emoji e altri personaggi che in realtà occupano più di una volta "personaggio" in un NSString.
rmaddy,

5

Swift3

È possibile utilizzare la sintassi del pedice per accedere al carattere in un determinato indice String.

let greeting = "Guten Tag!"
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index] // a

Visita https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/StringsAndCharacters.html

oppure possiamo fare un'estensione di stringa in Swift 4

extension String {
    func getCharAtIndex(_ index: Int) -> Character {
        return self[self.index(self.startIndex, offsetBy: index)]
    }
}

USO:

let foo = "ABC123"
foo.getCharAtIndex(2) //C

3

La mia soluzione è in una riga, supponendo che cadena sia la stringa e 4 sia l'ennesima posizione desiderata:

let character = cadena[advance(cadena.startIndex, 4)]

Semplice ... Suppongo che Swift includerà più cose sulle sottostringhe nelle versioni future.


1
Non è lo stesso var charAtIndex = string[advance(string.startIndex, 10)]della risposta di Sulthan?
Martin R,

Sì, è la stessa soluzione con un altro esempio come ha detto Sulthan. Ci scusiamo per il duplicato. :) È facile che due persone abbiano trovato allo stesso modo.
Julio César Fernández Muñoz,

3

Swift 3: un'altra soluzione (testata nel parco giochi)

extension String {
    func substr(_ start:Int, length:Int=0) -> String? {
        guard start > -1 else {
            return nil
        }

        let count = self.characters.count - 1

        guard start <= count else {
            return nil
        }

        let startOffset = max(0, start)
        let endOffset = length > 0 ? min(count, startOffset + length - 1) : count

        return self[self.index(self.startIndex, offsetBy: startOffset)...self.index(self.startIndex, offsetBy: endOffset)]
    }
}

Uso:

let txt = "12345"

txt.substr(-1) //nil
txt.substr(0) //"12345"
txt.substr(0, length: 0) //"12345"
txt.substr(1) //"2345"
txt.substr(2) //"345"
txt.substr(3) //"45"
txt.substr(4) //"5"
txt.substr(6) //nil
txt.substr(0, length: 1) //"1"
txt.substr(1, length: 1) //"2"
txt.substr(2, length: 1) //"3"
txt.substr(3, length: 1) //"4"
txt.substr(3, length: 2) //"45"
txt.substr(3, length: 3) //"45"
txt.substr(4, length: 1) //"5"
txt.substr(4, length: 2) //"5"
txt.substr(5, length: 1) //nil
txt.substr(5, length: -1) //nil
txt.substr(-1, length: -1) //nil

2

Aggiornamento per sottostringa rapida 2.0

public extension String {
    public subscript (i: Int) -> String {
        return self.substringWithRange(self.startIndex..<self.startIndex.advancedBy(i + 1))
    }

    public subscript (r: Range<Int>) -> String {
        get {
            return self.substringWithRange(self.startIndex.advancedBy(r.startIndex)..<self.startIndex.advancedBy(r.endIndex))
        }
    }

}

2

Penso che una risposta veloce per ottenere il primo personaggio potrebbe essere:

let firstCharacter = aString[aString.startIndex]

È molto elegante e performante rispetto a:

let firstCharacter = Array(aString.characters).first

Ma .. se vuoi manipolare e fare più operazioni con le stringhe potresti pensare di creare un'estensione ... c'è un'estensione con questo approccio, è abbastanza simile a quella già pubblicata qui:

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

subscript(integerIndex: Int) -> Character {
    let index = startIndex.advancedBy(integerIndex)
    return self[index]
}

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

}

MA È UN'IDEA TERRIBILE !!

L'estensione di seguito è terribilmente inefficiente. Ogni volta che si accede a una stringa con un numero intero, viene eseguita una funzione O (n) per far avanzare il suo indice iniziale. L'esecuzione di un loop lineare all'interno di un altro loop lineare significa che questo per loop è accidentalmente O (n2) - all'aumentare della lunghezza della stringa, il tempo impiegato da questo loop aumenta in modo quadratico.

Invece di farlo, potresti usare la raccolta di stringhe dei personaggi.


2

Swift 3

extension String {

    public func charAt(_ i: Int) -> Character {
        return self[self.characters.index(self.startIndex, offsetBy: i)]
    }

    public subscript (i: Int) -> String {
        return String(self.charAt(i) as Character)
    }

    public subscript (r: Range<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

    public subscript (r: CountableClosedRange<Int>) -> String {
        return substring(with: self.characters.index(self.startIndex, offsetBy: r.lowerBound)..<self.characters.index(self.startIndex, offsetBy: r.upperBound))
    }

}

uso

let str = "Hello World"
let sub = str[0...4]

Suggerimenti e trucchi di programmazione utili (scritti da me)


2

Ottieni e imposta sottoscrizione (stringa e sottostringa) - Swift 4.2

Swift 4.2, Xcode 10

Ho basato la mia risposta sulla risposta di @alecarlson . L'unica grande differenza è che puoi ottenere un Substringo un Stringreso (e in alcuni casi, un singolo Character). Puoi anche gete setil pedice. Infine, la mia è un po 'più ingombrante e più lunga della risposta di @alecarlson e come tale, suggerisco di inserirla in un file sorgente.


Estensione:

public extension String {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            self.replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}
public extension Substring {
    public subscript (i: Int) -> Character {
        get {
            return self[index(startIndex, offsetBy: i)]
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }

    }
    public subscript (bounds: CountableRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ..< end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return self[start ... end]
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ... end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return self[startIndex ..< end]
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }
    public subscript (i: Int) -> String {
        get {
            return "\(self[index(startIndex, offsetBy: i)])"
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
    public subscript (bounds: CountableRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ..< end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ..< end, with: s)
        }
    }
    public subscript (bounds: CountableClosedRange<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(start ... end, with: s)
        }

    }
    public subscript (bounds: CountablePartialRangeFrom<Int>) -> String {
        get {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            return "\(self[start ... end])"
        }
        set (s) {
            let start = index(startIndex, offsetBy: bounds.lowerBound)
            let end = index(endIndex, offsetBy: -1)
            replaceSubrange(start ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeThrough<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ... end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ... end, with: s)
        }
    }
    public subscript (bounds: PartialRangeUpTo<Int>) -> String {
        get {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            return "\(self[startIndex ..< end])"
        }
        set (s) {
            let end = index(startIndex, offsetBy: bounds.upperBound)
            replaceSubrange(startIndex ..< end, with: s)
        }
    }

    public subscript (i: Int) -> Substring {
        get {
            return Substring("\(self[index(startIndex, offsetBy: i)])")
        }
        set (c) {
            let n = index(startIndex, offsetBy: i)
            replaceSubrange(n...n, with: "\(c)")
        }
    }
}

Ciò compensa inutilmente entrambi gli indici (inizio e fine) da startIndex. Puoi semplicemente compensare l'indice finale usando range.count e compensare l'indice iniziale
Leo Dabus il

2

Rapido 4.2

Questa risposta è ideale perché si estende Stringe tutta la sua Subsequences( Substring) in un'unica estensione

public extension StringProtocol {

    public subscript (i: Int) -> Element {
        return self[index(startIndex, offsetBy: i)]
    }

    public subscript (bounds: CountableClosedRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start...end]
    }

    public subscript (bounds: CountableRange<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[start..<end]
    }

    public subscript (bounds: PartialRangeUpTo<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex..<end]
    }

    public subscript (bounds: PartialRangeThrough<Int>) -> SubSequence {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        return self[startIndex...end]
    }

    public subscript (bounds: CountablePartialRangeFrom<Int>) -> SubSequence {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        return self[start..<endIndex]
    }
}

uso

var str = "Hello, playground"

print(str[5...][...5][0])
// Prints ","

Ciò compensa inutilmente entrambi gli indici ( starte end) dal startIndex. Puoi semplicemente compensare l' endindice usando range.count e compensare l' startindice
Leo Dabus

2

Ormai l'abbonamento (_ :) non è disponibile. Inoltre non possiamo farlo

str[0] 

con string. Dobbiamo fornire "String.Index" Ma come possiamo dare il nostro numero indice in questo modo, invece possiamo usare,

string[str.index(str.startIndex, offsetBy: 0)]

Modifica la tua risposta e aggiungi un po 'di contesto spiegando come la tua risposta risolve il problema, invece di pubblicare una risposta solo in codice. Dalla recensione
Pedram Parsian,

Perché eseguire l'offset non necessario? Perché non semplicemente string[string.startIndex]? A proposito, il codice non si comporterebbe correttamente / compilerà, poiché hai usato due nomi di variabili diversi.
Cristik,

2

Swift 4.2 o successivo

Abbonamento e intervallo parziale mediante String'sindices proprietà di

Come variante della bella risposta di @LeoDabus , potremmo aggiungere un'estensione aggiuntiva allo DefaultIndicesscopo di consentirci di ricorrere alla indicesproprietà di Stringquando implementiamo gli script personalizzati (per Intintervalli specializzati e intervalli parziali) per quest'ultimo.

extension DefaultIndices {
    subscript(at: Int) -> Elements.Index { index(startIndex, offsetBy: at) }
}

// Moving the index(_:offsetBy:) to an extension yields slightly
// briefer implementations for these String extensions.
extension String {
    subscript(range: Range<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start..<indices[start...][range.count]]
    }
    subscript(range: ClosedRange<Int>) -> SubSequence {
        let start = indices[range.lowerBound]
        return self[start...indices[start...][range.count]]
    }
    subscript(range: PartialRangeFrom<Int>) -> SubSequence {
        self[indices[range.lowerBound]...]
    }
    subscript(range: PartialRangeThrough<Int>) -> SubSequence {
        self[...indices[range.upperBound]]
    }
    subscript(range: PartialRangeUpTo<Int>) -> SubSequence {
        self[..<indices[range.upperBound]]
    }
}

let str = "foo bar baz bax"
print(str[4..<6]) // "ba"
print(str[4...6]) // "bar"
print(str[4...])  // "bar baz bax"
print(str[...6])  // "foo bar"
print(str[..<6])  // "foo ba"

Grazie @LeoDabus per avermi indicato la direzione dell'uso della indicesproprietà come (altra) alternativa alla Stringsottoscrizione!


1
l'unico svantaggio è CountableClosedRange compenserà entrambi gli indici
dall'inizioIndex

1
@LeoDabus vedo. Sì, soprattutto Linux, ma non molto Swift in questi giorni: / Lo uso swiftenvquando lo faccio, però, suppongo che verrà aggiornato con 4.2 anche un po 'presto allora.
venerdì

1
@LeoDabus grazie per aver aggiornato questa risposta alla moderna Swift!
venerdì

1
@LeoDabus bel lavoro! Dovremo esaminare i dettagli in un secondo momento, ma ricordo che non mi è mai piaciuto che dovessimo ricorrere alla Fondazione per alcuni dei tipi di set ordinati / numerabili.
venerdì

1
grazie compagno!!!
Leo Dabus,

2

Swift 5.1.3:

Aggiungi un'estensione String:

extension String {

 func stringAt(_ i: Int) -> String { 
   return String(Array(self)[i]) 
 } 

 func charAt(_ i: Int) -> Character { 
  return Array(self)[i] 
 } 
}

let str = "Teja Kumar"
let str1: String = str.stringAt(2)  //"j"
let str2: Character = str.charAt(5)  //"k"

1
Questo convertirà l'intera stringa in una matrice di caratteri ogni volta che chiami questa proprietà per estrarne un singolo carattere.
Leo Dabus,

1

Il Stringtipo di Swift non fornisce acharacterAtIndex metodo perché ci sono diversi modi in cui una stringa Unicode può essere codificata. Stai andando con UTF8, UTF16 o qualcos'altro?

È possibile accedere alle CodeUnitraccolte recuperando le proprietà String.utf8e String.utf16. È inoltre possibile accedere alla UnicodeScalarraccolta recuperando ilString.unicodeScalars proprietà.

Nello spirito NSStringdell'implementazione, sto restituendo un unichartipo.

extension String
{
    func characterAtIndex(index:Int) -> unichar
    {
        return self.utf16[index]
    }

    // Allows us to use String[index] notation
    subscript(index:Int) -> unichar
    {
        return characterAtIndex(index)
    }
}

let text = "Hello Swift!"
let firstChar = text[0]

Ciò fallirà per i caratteri che richiedono più spazio di archiviazione di 16 bit. Fondamentalmente, qualsiasi carattere Unicode oltre U + FFFF.
rmaddy,

1

Al fine di alimentare il soggetto e mostrare possibilità di sottoscrizione rapida, ecco una piccola stringa basata su pedice "substring-toolbox"

Questi metodi sono sicuri e non vanno mai oltre gli indici di stringa

extension String {
    // string[i] -> one string char
    subscript(pos: Int) -> String { return String(Array(self)[min(self.length-1,max(0,pos))]) }

    // string[pos,len] -> substring from pos for len chars on the left
    subscript(pos: Int, len: Int) -> String { return self[pos, len, .pos_len, .left2right] }

    // string[pos, len, .right2left] -> substring from pos for len chars on the right
    subscript(pos: Int, len: Int, way: Way) -> String { return self[pos, len, .pos_len, way] }

    // string[range] -> substring form start pos on the left to end pos on the right
    subscript(range: Range<Int>) -> String { return self[range.startIndex, range.endIndex, .start_end, .left2right] }

    // string[range, .right2left] -> substring start pos on the right to end pos on the left
    subscript(range: Range<Int>, way: Way) -> String { return self[range.startIndex, range.endIndex, .start_end, way] }

    var length: Int { return countElements(self) }
    enum Mode { case pos_len, start_end }
    enum Way { case left2right, right2left }
    subscript(var val1: Int, var val2: Int, mode: Mode, way: Way) -> String {
        if mode == .start_end {
            if val1 > val2 { let val=val1 ; val1=val2 ; val2=val }
            val2 = val2-val1
        }
        if way == .left2right {
            val1 = min(self.length-1, max(0,val1))
            val2 = min(self.length-val1, max(1,val2))
        } else {
            let val1_ = val1
            val1 = min(self.length-1, max(0, self.length-val1_-val2 ))
            val2 = max(1, (self.length-1-val1_)-(val1-1) )
        }
        return self.bridgeToObjectiveC().substringWithRange(NSMakeRange(val1, val2))

        //-- Alternative code without bridge --
        //var range: Range<Int> = pos...(pos+len-1)
        //var start = advance(startIndex, range.startIndex)
        //var end = advance(startIndex, range.endIndex)
        //return self.substringWithRange(Range(start: start, end: end))
    }
}


println("0123456789"[3]) // return "3"

println("0123456789"[3,2]) // return "34"

println("0123456789"[3,2,.right2left]) // return "56"

println("0123456789"[5,10,.pos_len,.left2right]) // return "56789"

println("0123456789"[8,120,.pos_len,.right2left]) // return "01"

println("0123456789"[120,120,.pos_len,.left2right]) // return "9"

println("0123456789"[0...4]) // return "01234"

println("0123456789"[0..4]) // return "0123"

println("0123456789"[0...4,.right2left]) // return "56789"

println("0123456789"[4...0,.right2left]) // return "678" << because ??? range can wear endIndex at 0 ???

1

Una soluzione simile a Python, che ti consente di utilizzare l'indice negativo,

var str = "Hello world!"
str[-1]        // "!"

potrebbe essere:

extension String {
    subscript (var index:Int)->Character{
        get {
            let n = distance(self.startIndex, self.endIndex)
            index %= n
            if index < 0 { index += n }
            return self[advance(startIndex, index)]
        }
    }
}

A proposito, potrebbe valere la pena trasporre l'intera notazione di slice di Python


Ti dispiace scrivere qualcosa che compila per Swift 4? ultimo ritorno ... la linea non sembra funzionare in anticipo () la funzione non è lì credo.
C0D3,

1

Puoi anche convertire String in Matrice di caratteri in questo modo:

let text = "My Text"
let index = 2
let charSequence = text.unicodeScalars.map{ Character($0) }
let char = charSequence[index]

Questo è il modo per ottenere il carattere all'indice specificato in tempo costante.

L'esempio seguente non funziona a tempo costante, ma richiede un tempo lineare. Quindi, se hai molte ricerche in String per indice, usa il metodo sopra.

let char = text[text.startIndex.advancedBy(index)]
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.