Obtener el enésimo carácter de una cadena en lenguaje de programación Swift

419

¿Cómo puedo obtener el enésimo carácter de una cadena? Probé el []accesorio de soporte ( ) sin suerte.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ERROR: 'subíndice' no está disponible: no puede subíndice Cadena con un Int, vea el comentario de la documentación para su discusión

Mohsen
fuente
1
El mensaje de error "no puede subíndice String con un Int, vea el comentario de la documentación para discusión" parece referirse a github.com/apple/swift/blob/master/stdlib/public/core/…
andrewdotn
utilizar var firstChar = string.index(string.startIndex, offsetBy: 0)en su lugar
Sazzad Hissain Khan
@SazzadHissainKhan esto daría como resultado un índice de cadena, no un carácter. Por cierto, ¿por qué no simplemente string.startIndex? Para el primer personaje string[string.startIndex] o simplemente string.first. Tenga en cuenta que el primer enfoque que necesitaría para verificar si la cadena está vacía primero, el segundo devuelve un opcional
Leo Dabus el

Respuestas:

567

Atención: consulte la respuesta de Leo Dabus para una implementación adecuada de Swift 4 y Swift 5.

Swift 4 o posterior

los Substring tipo se introdujo en Swift 4 para hacer que las subcadenas sean más rápidas y más eficientes al compartir el almacenamiento con la cadena original, por lo que las funciones de subíndice deberían volver.

Pruébalo aquí

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

Para convertirlo Substringen a String, simplemente puede hacerlo String(string[0..2]), pero solo debe hacerlo si planea mantener la subcadena alrededor. De lo contrario, es más eficiente mantenerlo a Substring.

Sería genial si alguien pudiera encontrar una buena manera de combinar estas dos extensiones en una sola. Intenté extender StringProtocol sin éxito, porque el indexmétodo no existe allí. Nota: Esta respuesta ya se ha editado, está implementada correctamente y ahora también funciona para las subcadenas. Solo asegúrese de usar un rango válido para evitar bloqueos al suscribir su tipo StringProtocol. Para suscribirse a un rango que no se bloqueará con valores fuera de rango, puede usar esta implementación


¿Por qué esto no está incorporado?

El mensaje de error dice "ver el comentario de la documentación para la discusión" . Apple proporciona la siguiente explicación en el archivo UnavailableStringAPIs.swift :

La suscripción de cadenas con enteros no está disponible.

El concepto de "el icarácter th en una cadena" tiene diferentes interpretaciones en diferentes bibliotecas y componentes del sistema. La interpretación correcta se debe seleccionar de acuerdo con el caso de uso y las API involucradas, por String lo que no se puede suscribir con un entero.

Swift proporciona varias formas diferentes de acceder a los datos de caracteres almacenados dentro de las cadenas.

  • String.utf8es una colección de unidades de código UTF-8 en la cadena. Use esta API cuando convierta la cadena a UTF-8. La mayoría de las API POSIX procesan cadenas en términos de unidades de código UTF-8.

  • String.utf16es una colección de unidades de código UTF-16 en cadena. La mayoría de las API de Cocoa y Cocoa touch procesan cadenas en términos de unidades de código UTF-16. Por ejemplo, instancias de NSRangeutilizado con NSAttributedStringy NSRegularExpressionalmacenar compensaciones de subcadenas y longitudes en términos de unidades de código UTF-16.

  • String.unicodeScalarses una colección de escalares Unicode. Use esta API cuando realice una manipulación de bajo nivel de datos de caracteres.

  • String.characters es una colección de grupos de grafemas extendidos, que son una aproximación de los caracteres percibidos por el usuario.

Tenga en cuenta que cuando se procesan cadenas que contienen texto legible por humanos, se debe evitar el procesamiento carácter por carácter en la mayor medida posible. Uso de alto nivel sensible a la localidad Unicode algoritmos en lugar, por ejemplo, String.localizedStandardCompare(), String.localizedLowercaseString, String.localizedStandardRangeOfString()etc.

aleclarson
fuente
44
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.Indexy Intno son compatibles.
77
Si ve, Cannot subscript a value of type 'String'...verifique esta respuesta: stackoverflow.com/a/31265316/649379
SoftDesigner
24
Cuando trato de usar esto, me sale Ambiguous use of 'subscript'.
Jowie
18
¡ADVERTENCIA! La extensión a continuación es terriblemente ineficiente. Cada vez que se accede a una cadena con un número entero, se ejecuta una función O (n) para avanzar su índice inicial. Ejecutar un bucle lineal dentro de otro bucle lineal significa que este bucle for accidentalmente es O (n2); a medida que aumenta la longitud de la cadena, el tiempo que tarda este bucle aumenta de forma cuadrática. En lugar de hacerlo, puede usar la colección de cadenas de caracteres.
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"

Deberá agregar esta extensión de cadena a su proyecto (está completamente probado):

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

Aunque Swift siempre tuvo una solución inmediata a este problema (sin la extensión String, que proporcioné a continuación), todavía recomendaría encarecidamente usar la extensión. ¿Por qué? Debido a que me ahorró decenas de horas de migración dolorosa de las primeras versiones de Swift, donde la sintaxis de String estaba cambiando en casi todas las versiones, pero todo lo que tenía que hacer era actualizar la implementación de la extensión en lugar de refactorizar todo el proyecto. Haz tu elección.

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"
nalexn
fuente
cambiar range.upperBound - range.lowerBoundarange.count
Leo Dabus el
No es parte de la pregunta original, pero ... sería bueno si esta asignación también fuera compatible. Por ejemplo, s [i] = "a" :).
Chris Prince
2
Creo que con los subíndices Swift 4.2 no están disponibles nuevamente. Recibo un error que dice: 'subíndice' no está disponible: no se puede subíndice Cadena con un Int, vea el comentario de la documentación para su discusión
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
Esto debería ser funciones incorporadas
Ammar Mujeeb
150

Se me ocurrió esta solución clara.

var firstChar = Array(string)[0]
Jens Wirth
fuente
2
Esta es una buena solución rápida para el caso (común) en el que sabe que tiene cadenas codificadas UTF8 o ASCII. Solo asegúrese de que las cadenas nunca estarán en una codificación que use más de un byte.
Jeff Hay
48
Eso parece extremadamente ineficiente ya que está copiando toda la cadena solo para obtener el primer carácter. Usa string [string. startIndex] en lugar de 0 como señaló Sulthan.
Bjorn el
66
Separar cadena: var firstChar = Array(string!)[0]De lo contrario, diráadd arrayLiteral
Mohammad Zaid Pathan
2
No creo que esto esté limpio, de hecho es una ronda. No estoy muy seguro de qué inicializador en Array se usa primero haciendo que esto suceda (y supongo que es el inicializador SequenceType que hace que reúna los caracteres de la cadena como componentes individuales para el Array). Esto no es explícito en absoluto y puede corregirse en el futuro sin tipo de conversión. Esto tampoco funciona si usa la taquigrafía para la matriz a través de [string] .first. La solución de @ Sulthan funciona mejor para usar los valores de índice horneados. Es mucho más claro lo que está sucediendo aquí.
TheCodingArt
2
¡Guau, falla del segmento del compilador!
Frank
124

Sin indexación con enteros, solo con String.Index. Principalmente con complejidad lineal. También puede crear rangos desdeString.Index y obtener subcadenas con ellos.

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]

Tenga en cuenta que nunca puede usar un índice (o rango) creado de una cadena a otra cadena

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]
Sulthan
fuente
77
StringLos índices son exclusivos de una cadena. Esto se debe a que diferentes cadenas pueden tener diferentes UTF-16 de múltiples unidades Charactersy / o en diferentes posiciones, por lo que los índices de la unidad UTF-16 no coincidirán, pueden caer más allá del final o punto dentro de un UTF-16 de múltiples unidades Character.
zaph
@Zaph Eso es obvio.
Sulthan
3
Explicando por qué dice: "a veces se bloqueará o dará como resultado un comportamiento indefinido". Quizás sea mejor decir que no lo hagas porque ...
zaph
1
@Sulthan ..es ahora ..<(en su asignación a range)
Aaron Brager
3
@CajunLuke Sé que ha pasado un tiempo desde que publicaste este comentario, pero mira esta respuesta . Puedes usarvar lastChar = string[string.endIndex.predecessor()]
David L
122

Xcode 11 • Swift 5.1

Puede extender StringProtocol para que el subíndice esté disponible también para las subcadenas:

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

Pruebas

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 🇧🇷!!!"
Leo Dabus
fuente
¿Puedo preguntar qué es "self [index (startIndex, offsetBy: i)]"? ¿Y cómo funciona "self [i]"?
allenlinli
1
Hola Leo, gracias por la solución! Simplemente (hoy) cambié de Swift 2.3 a 3 y su subíndice de solución (rango: Rango <Int>) da el error "Argumento adicional 'limitedBy' en la llamada". ¿Qué crees que puede estar mal?
Ahmet Akkök
@ AhmetAkkök, ¿estás seguro de que no cambiaste el código?
Leo Dabus el
1
@Leo resultó que no convertí todo el proyecto, pero en la aplicación, no en la extensión, he repetido el proceso tanto para la aplicación como para la extensión y ahora funciona bien. ¡Su ayuda es muy apreciada!
Ahmet Akkök
Este es un código muy complicado. ¿Cuál es la ventaja sobre hacer return String(Array(characters)[range])en Swift 3?
Dan Rosenstark el
69

Swift 4

let str = "My String"

Cadena en el índice

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

Subcadena

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

Primero n caracteres

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

Últimos n caracteres

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

Swift 2 y 3

str = "My String"

** Cadena en el índice **

Swift 2

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

Swift 3

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

Subcadena de índice a índice

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

Primero n caracteres

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

Últimos n caracteres

let last3Chars = String(str.characters.suffix(3)) // last3Chars = "ing"
Warif Akhand Rishi
fuente
24

Swift 2.0 a partir de Xcode 7 GM Seed

var text = "Hello, world!"

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

Para el enésimo carácter, reemplace 0 con n-1.

Editar: Swift 3.0

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


nb hay formas más simples de agarrar ciertos caracteres en la cadena

p.ej let firstChar = text.characters.first

Matt Le Fleur
fuente
23

Si ves Cannot subscript a value of type 'String'...usa esta extensión:

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

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

SoftDesigner
fuente
19

Solución Swift 2.2:

La siguiente extensión funciona en Xcode 7, esta es una combinación de esta solución y la conversión de sintaxis 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]
    }
}
Dan Beaulieu
fuente
14

La clase de cadena rápida no proporciona la capacidad de obtener un carácter en un índice específico debido a su soporte nativo para caracteres UTF. La longitud variable de un personaje UTF en la memoria hace que sea imposible saltar directamente a un personaje. Eso significa que tienes que recorrer manualmente la cadena cada vez.

Puede extender String para proporcionar un método que recorra los caracteres hasta el índice deseado

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)!
Drewag
fuente
3
Ya puede recorrer las cadenas: para la letra en "foo" {println (letra)}
Doobeh
@Doobeh quise decir recorrer y devolver el personaje real como en mi edición anterior
drewag
¡bonito! Es curioso cómo puedes iterar a través de él, pero no a través de un índice. Swift se siente pitónico pero con bordes más duros.
Doobeh
Encontré que usar myString.bridgeToObjectiveC().characterAtIndex(0)o (string as NSString ).characterAtIndex(0) devuelve un valor Int del personaje
markhunte
44
No use NSStringmétodos para acceder a caracteres individuales de un nativo de Swift String: los dos usan mecanismos de conteo diferentes, por lo que obtendrá resultados impredecibles con caracteres Unicode más altos. El primer método debe ser seguro (una vez que se manejan los errores de Unicode de Swift).
Nate Cook
11

Como nota al margen, hay algunas funciones aplicables directamente a la representación de la cadena de caracteres de una cadena, como esta:

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

El resultado es de tipo Carácter, pero puede convertirlo en una Cadena.

O esto:

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

:-)

Frédéric Adda
fuente
11

Swift 4

String(Array(stringToIndex)[index]) 

Esta es probablemente la mejor manera de resolver este problema una vez. Probablemente desee convertir el String como una matriz primero, y luego lanzar el resultado como un String nuevamente. De lo contrario, se devolverá un Carácter en lugar de una Cadena.

El ejemplo String(Array("HelloThere")[1])devolverá "e" como una cadena.

(Array("HelloThere")[1] devolverá "e" como un personaje.

Swift no permite que las cadenas se indexen como matrices, pero esto hace el trabajo, estilo de fuerza bruta.

Harsh G.
fuente
1
Duplicar todo el contenido de la cadena a otra ubicación de memoria es contrarrevolucionario, especialmente para cadenas grandes. No deberíamos necesitar asignaciones de memoria adicionales para tareas simples como el acceso directo a la memoria.
Cristik
7

Mi solución muy simple:

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)]
Linh Dao
fuente
Funciona en Swift 4.1
leanne
La solución más simple y ahora con el ejemplo de Swift 5 :)
OhadM
@Linh Dao No use encodedOffset. encodedOffset está en desuso: encodedOffset ha quedado en desuso porque el uso más común es incorrecto.
Leo Dabus
@OhadM más simple no significa que sea correcto o al menos no funcionará como espera. Intentelet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Leo Dabus
1
@OhadM Mi comentario fue solo una advertencia. Siéntase libre de usarlo si cree que se comporta como espera.
Leo Dabus
6

Puede hacerlo convirtiendo String en Array y obteniéndolo por índice específico usando el subíndice como se muestra a continuación

var str = "Hello"
let s = Array(str)[2]
print(s)
Soeng Saravit
fuente
1
Tenga en cuenta que esta solución dará como resultado la duplicación de contenido, lo que la hará menos eficiente en cuanto a memoria y CPU.
Cristik
5

Acabo de tener el mismo problema. Simplemente haz esto:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)
usuario3723247
fuente
Esto falla para muchos Emoji y otros personajes que realmente ocupan más de una vez "personaje" en un NSString.
rmaddy
5

Swift3

Puede usar la sintaxis de subíndice para acceder al Carácter en un índice de Cadena particular.

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

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

o podemos hacer una extensión de cadena en 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
Hamed
fuente
3

Mi solución está en una línea, suponiendo que cadena es la cadena y 4 es la enésima posición que desea:

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

Simple ... supongo que Swift incluirá más cosas sobre subcadenas en futuras versiones.

Julio César Fernández Muñoz
fuente
1
¿No es lo mismo que var charAtIndex = string[advance(string.startIndex, 10)]en la respuesta de Sulthan?
Martin R
Sí, es la misma solución con otro ejemplo como el de Sulthan. Perdón por el duplicado. :) Es fácil encontrar a dos personas de la misma manera.
Julio César Fernández Muñoz
3

Swift 3: otra solución (probado en el patio de recreo)

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
Peter Kreinz
fuente
2

Actualización para swift 2.0 subString

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

}
YannSteph
fuente
2

Creo que una respuesta rápida para obtener el primer personaje podría ser:

let firstCharacter = aString[aString.startIndex]

Es mucho más elegante y de rendimiento que:

let firstCharacter = Array(aString.characters).first

Pero ... si desea manipular y realizar más operaciones con cadenas, podría pensar crear una extensión ... hay una extensión con este enfoque, es bastante similar a la que ya se publicó aquí:

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

}

¡PERO ES UNA IDEA TERRIBLE!

La extensión a continuación es terriblemente ineficiente. Cada vez que se accede a una cadena con un número entero, se ejecuta una función O (n) para avanzar su índice de inicio. Ejecutar un bucle lineal dentro de otro bucle lineal significa que este bucle for accidentalmente es O (n2); a medida que aumenta la longitud de la cadena, el tiempo que tarda este bucle aumenta de forma cuadrática.

En lugar de hacerlo, puede usar la colección de cadenas de caracteres.

ignaciohugog
fuente
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]

Consejos y trucos útiles de programación (escritos por mí)

malicioso
fuente
2

Obtener y establecer subíndice (cadena y subcadena) - Swift 4.2

Swift 4.2, Xcode 10

Basé mi respuesta en la respuesta de @alecarlson . La única gran diferencia es que puede obtener una Substringo una Stringdevolución (y en algunos casos, una sola Character). También puedes gety setel subíndice. Por último, la mía es un poco más engorrosa y más larga que la respuesta de @alecarlson y, como tal, sugiero que la coloque en un archivo fuente.


Extensión:

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)")
        }
    }
}
Noah Wilder
fuente
Esto compensa innecesariamente ambos índices (inicio y fin) del índice de inicio. Simplemente puede compensar el índice final usando range.count y compensar el índice inicial
Leo Dabus
2

Swift 4.2

Esta respuesta es ideal porque se extiende Stringy todo su Subsequences( Substring) en una extensión

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 ","
Noah Wilder
fuente
Esto compensa innecesariamente ambos índices ( starty end) del startIndex. Simplemente puede compensar el endíndice usando range.count y compensar el startíndice
Leo Dabus
2

Por ahora, el subíndice (_ :) no está disponible. Así como no podemos hacer esto

str[0] 

con cadena. Tenemos que proporcionar "String.Index" Pero, ¿cómo podemos dar nuestro propio número de índice de esta manera, en su lugar podemos usar,

string[str.index(str.startIndex, offsetBy: 0)]
Yodagama
fuente
Edite su respuesta y agregue algo de contexto explicando cómo su respuesta resuelve el problema, en lugar de publicar una respuesta de solo código. De la opinión
Pedram Parsian
¿Por qué hacer la compensación innecesaria? ¿Por qué no simplemente string[string.startIndex]? Por cierto, el código no se comportaría / compilaría correctamente, ya que usó dos nombres de variables diferentes.
Cristik
2

Swift 4.2 o posterior

Rango y suscripción parcial de rango usando String'sindices propiedad de

Como variación de la buena respuesta de @LeoDabus , podemos agregar una extensión adicional DefaultIndicescon el fin de permitirnos recurrir a la indicespropiedad de Stringcuando se implementan los subíndices personalizados (por Intrangos especializados y rangos parciales) para este último.

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"

¡Gracias @LeoDabus por señalarme en la dirección de usar la indicespropiedad como (otra) alternativa a la Stringsuscripción!

dfri
fuente
1
la única desventaja es que CountableClosedRange compensará ambos índices desde el inicio Índice
Leo Dabus
1
@LeoDabus Ya veo. Sí, principalmente Linux, pero no mucho Swift en estos días: Sin swiftenvembargo, lo uso cuando lo hago, supongo que se actualizará con 4.2 también un poco pronto.
dfri
1
@LeoDabus gracias por actualizar esta respuesta a Swift moderno!
dfri
1
@LeoDabus buen trabajo! Tendré que analizar los detalles más adelante, pero recuerdo que nunca me gustó que tuviéramos que recurrir a Foundation algunos de los tipos de conjuntos ordenados / contables.
dfri
1
¡¡¡gracias amigo!!!
Leo Dabus el
2

Swift 5.1.3:

Agregue una extensión de cadena:

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"
Teja Kumar Bethina
fuente
1
Esto convertirá la cadena completa en una matriz de caracteres cada vez que llame a esta propiedad para extraer un solo carácter de ella.
Leo Dabus
1

El Stringtipo de Swift no proporciona un characterAtIndexmétodo porque hay varias formas de codificar una cadena Unicode. ¿Vas con UTF8, UTF16 u otra cosa?

Puede acceder a las CodeUnitcolecciones recuperando las propiedades String.utf8y String.utf16. También puede acceder a la UnicodeScalarcolección recuperando la String.unicodeScalarspropiedad.

En el espíritu de NSStringimplementación, estoy devolviendo 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]
Erik
fuente
Esto fallará para los caracteres que necesitan más almacenamiento de 16 bits. Básicamente, cualquier personaje Unicode más allá de U + FFFF.
rmaddy
1

Para alimentar el tema y mostrar posibilidades rápidas de subíndice, aquí hay una pequeña cadena basada en el subíndice "substring-toolbox"

Estos métodos son seguros y nunca pasan por encima de los índices de cadena

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 ???
Luc-Olivier
fuente
1

Una solución similar a Python, que le permite usar índice negativo,

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

podría ser:

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

Por cierto, puede valer la pena transponer toda la notación de corte de la pitón

Joseph Merdrignac
fuente
¿Te importaría escribir algo que compile para Swift 4? último retorno ... la línea no parece funcionar la función de avance () no está allí, creo.
C0D3
1

También puede convertir String a Array of Characters de esa manera:

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

Esta es la forma de obtener char en el índice especificado en tiempo constante.

El siguiente ejemplo no se ejecuta en tiempo constante, pero requiere tiempo lineal. Entonces, si tiene muchas búsquedas en String por índice, use el método anterior.

let char = text[text.startIndex.advancedBy(index)]
Marcin Kapusta
fuente