Obter o enésimo caractere de uma string na linguagem de programação Swift

419

Como posso obter o enésimo caractere de uma string? Eu tentei o []acessador bracket ( ) sem sorte.

var string = "Hello, world!"

var firstChar = string[0] // Throws error

ERRO: 'subscript' está indisponível: não é possível subscrever String com um Int, consulte o comentário da documentação para discussão

Mohsen
fonte
1
A mensagem de erro "não é possível subscrever String com um Int, consulte o comentário da documentação para discussão" parece estar se referindo a github.com/apple/swift/blob/master/stdlib/public/core/…
andrewdotn
use em var firstChar = string.index(string.startIndex, offsetBy: 0)vez disso
Sazzad Hissain Khan 22/19/19
@SazzadHissainKhan isso resultaria em um índice de string, não em um caractere. Btw porque não simplesmente string.startIndex? Para o primeiro personagem string[string.startIndex] ou simplesmente string.first. Note-se que a primeira abordagem, você precisa verificar se a cadeia está vazia pela primeira vez o segundo retorna um opcional
Leo Dabus

Respostas:

567

Atenção: Por favor, veja a resposta de Leo Dabus para uma implementação adequada do Swift 4 e Swift 5.

Swift 4 ou posterior

O Substringtipo foi introduzido no Swift 4 para tornar as substrings mais rápidas e mais eficientes, compartilhando o armazenamento com a string original, e é isso que as funções de subscrito devem retornar.

Experimente aqui

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 converter o Substringem a String, você pode simplesmente fazer String(string[0..2]), mas só deve fazer isso se planejar manter a substring por perto. Caso contrário, é mais eficiente mantê-lo a Substring.

Seria ótimo se alguém descobrisse uma boa maneira de mesclar essas duas extensões em uma. Eu tentei estender StringProtocol sem sucesso, porque o indexmétodo não existe lá. Nota: Esta resposta já foi editada, está implementada corretamente e agora também funciona para substrings. Apenas certifique-se de usar um intervalo válido para evitar falhas ao inscrever seu tipo StringProtocol. Para assinar com um intervalo que não trava com valores fora do intervalo, você pode usar esta implementação


Por que isso não está embutido?

A mensagem de erro diz "consulte o comentário da documentação para discussão" . A Apple fornece a seguinte explicação no arquivo UnavailableStringAPIs.swift :

A assinatura de seqüências de caracteres com números inteiros não está disponível.

O conceito de "o ith caractere em uma string" tem diferentes interpretações em diferentes bibliotecas e componentes do sistema. A interpretação correta deve ser selecionada de acordo com o caso de uso e as APIs envolvidas, portanto, String não pode ser subscrito com um número inteiro.

O Swift fornece várias maneiras diferentes de acessar os dados de caracteres armazenados nas cadeias.

  • String.utf8é uma coleção de unidades de código UTF-8 na cadeia. Use esta API ao converter a sequência em UTF-8. A maioria das APIs POSIX processa cadeias de caracteres em termos de unidades de código UTF-8.

  • String.utf16é uma coleção de unidades de código UTF-16 em string. A maioria das APIs de cacau e cacau touch processa cadeias de caracteres em termos de unidades de código UTF-16. Por exemplo, instâncias NSRangeusadas com NSAttributedStringe NSRegularExpressionarmazenam deslocamentos e comprimentos de substring em termos de unidades de código UTF-16.

  • String.unicodeScalarsé uma coleção de escalares Unicode. Use esta API quando estiver executando manipulação de baixo nível de dados de caracteres.

  • String.characters é uma coleção de clusters de grafema estendidos, que são uma aproximação dos caracteres percebidos pelo usuário.

Observe que, ao processar seqüências de caracteres que contenham texto legível por humanos, o processamento de caractere por caractere deve ser evitado na maior extensão possível. De alto nível uso sensíveis à localidade algoritmos Unicode em vez disso, por exemplo, String.localizedStandardCompare(), String.localizedLowercaseString, String.localizedStandardRangeOfString()etc.

aleclarson
fonte
4
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'... String.Indexe Intnão são compatíveis.
7
Se aparecer, Cannot subscript a value of type 'String'...verifique esta resposta: stackoverflow.com/a/31265316/649379
SoftDesigner 07/07/2015
24
Quando tento usar isso, recebo Ambiguous use of 'subscript'.
jowie
18
ATENÇÃO! A extensão abaixo é terrivelmente ineficiente. Toda vez que uma string é acessada com um número inteiro, uma função O (n) para avançar seu índice inicial é executada. A execução de um loop linear dentro de outro loop linear significa que esse loop for é acidentalmente O (n2) - à medida que o comprimento da string aumenta, o tempo que esse loop leva aumenta quadraticamente. Em vez de fazer isso, você pode usar a coleção de cadeias 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"

Você precisará adicionar esta extensão String ao seu projeto (ela foi totalmente testada):

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

Embora o Swift sempre tivesse uma solução pronta para esse problema (sem a extensão String, que forneci abaixo), eu ainda recomendaria fortemente o uso da extensão. Por quê? Porque isso me salvou dezenas de horas de migração dolorosa das versões anteriores do Swift, onde a sintaxe do String estava mudando em quase todos os lançamentos, mas tudo que eu precisava fazer era atualizar a implementação da extensão, em vez de refatorar todo o projeto. Faça a sua escolha.

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
fonte
alterar range.upperBound - range.lowerBoundpararange.count
Leo Dabus 25/10
Não faz parte da pergunta original, mas ... seria bom se essa tarefa também fosse suportada. Por exemplo, s [i] = "a" :).
Chris Príncipe
2
Acredito que com os subscritos do Swift 4.2 não estejam disponíveis novamente. Eu recebo um erro dizendo: 'subscrito' não está disponível: não é possível subscrever String com um Int, consulte o comentário da documentação para discussão
C0D3 14/18
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
Isso deve ser funções
internas
150

Eu só vim com essa solução legal

var firstChar = Array(string)[0]
Jens Wirth
fonte
2
Essa é uma boa solução rápida para o caso (comum) em que você sabe que possui cadeias codificadas em UTF8 ou ASCII. Apenas certifique-se de que as seqüências nunca estejam em uma codificação que use mais de um byte.
21714 Jeff Jeff
48
Isso parece extremamente ineficiente, pois você está copiando a sequência inteira apenas para obter o primeiro caractere. Use string [string. startIndex] em vez de 0, como Sulthan apontou.
Bjorn
6
Corda Unwrap: var firstChar = Array(string!)[0]Caso contrário ele vai dizeradd arrayLiteral
Mohammad Zaid Pathan
2
Eu não acredito que isso seja limpo, na verdade é uma rodada. Não tenho certeza de qual inicializador em Matriz é usado primeiro, fazendo com que isso aconteça (e suponho que seja o inicializador SequenceType fazendo com que ele reúna os caracteres da sequência como componentes individuais da Matriz). Isso não é explícito e pode ser corrigido no futuro sem conversão de tipo. Isso também não funciona se você usar atalhos para a matriz via [string] .first. A solução da @ Sulthan funciona melhor para usar os valores de índice cozido. É muito mais claro o que está acontecendo aqui.
TheCodingArt
2
Uau, falha no segmento do compilador!
Frank
124

Sem indexação usando números inteiros, apenas usando String.Index. Principalmente com complexidade linear. Você também pode criar intervalos String.Indexe obter substrings usando-os.

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]

Observe que você nunca pode usar um índice (ou intervalo) criado de uma sequência para outra

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
fonte
7
Stringíndices são exclusivos para uma string. Isso ocorre porque cadeias diferentes podem ter UTF-16 de várias unidades diferentes Characterse / ou em posições diferentes, para que os índices da unidade UTF-16 não correspondam, podem cair além do final ou ponto dentro de uma UTF-16 de várias unidades Character.
Zaph 19/08/14
@ Zaph Isso é óbvio.
precisa
3
Explicando por que você diz: "às vezes falha ou resulta em um comportamento indefinido". Talvez melhor dizer simplesmente não fazê-lo porque ...
Zaph
1
@Sulthan ..é agora ..<(na sua atribuição a range)
Aaron Brager
3
@CajunLuke Eu sei que já faz um tempo desde que você postou este comentário, mas dê uma olhada nesta resposta . Você pode usarvar lastChar = string[string.endIndex.predecessor()]
David L
122

Xcode 11 • Swift 5.1

Você pode estender o StringProtocol para disponibilizar o subscrito também para as substrings:

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

Teste

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
fonte
Posso perguntar o que é "self [index (startIndex, offsetBy: i)]"? E como funciona o "eu [i]"?
allenlinli
1
Olá Leo, obrigado pela solução! Acabei de mudar (hoje) do Swift 2.3 para o 3 e o índice da sua solução (range: Range <Int>) fornece o erro "Argumento extra 'limitedBy' in call". O que você acha que pode estar errado?
Ahmet Akkök
@ AhmetAkkök tem certeza de que não mudou o código?
Leo Dabus
1
@Leo acabou que não converti o projeto inteiro, mas no aplicativo, não na extensão, repeti o processo para o aplicativo e a extensão e agora funciona bem. Sua ajuda é muito apreciada!
Ahmet Akkök
esse é um código muito complicado. Qual é a vantagem de fazer return String(Array(characters)[range])no Swift 3?
9306 Dan Rosenstark
69

Swift 4

let str = "My String"

String no índice

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"

Primeiros 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 e 3

str = "My String"

** String no índice **

Swift 2

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

Swift 3

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

SubString deIndex paraIndex

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

Primeiros 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
fonte
24

Swift 2.0 a partir da Xcode 7 GM Seed

var text = "Hello, world!"

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

Para o enésimo caractere, substitua 0 por n-1.

Edição: Swift 3.0

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


nb, existem maneiras mais simples de pegar certos caracteres na string

por exemplo let firstChar = text.characters.first

Matt Le Fleur
fonte
23

Se você vir Cannot subscript a value of type 'String'...usar esta extensão:

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/

SoftDesigner
fonte
19

Solução Swift 2.2:

A extensão a seguir funciona no Xcode 7, é uma combinação dessa solução e da conversão de sintaxe do 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
fonte
14

A classe de cadeia rápida não fornece a capacidade de obter um caractere em um índice específico devido ao seu suporte nativo a caracteres UTF. O comprimento variável de um caractere UTF na memória torna impossível pular diretamente para um caractere. Isso significa que você deve repetir manualmente a seqüência de caracteres toda vez.

Você pode estender String para fornecer um método que percorrerá os caracteres até o índice desejado

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
fonte
3
Você já pode percorrer strings: por carta em "foo" {println (letra)}
Doobeh
@Doobeh eu quis dizer percorrer e retornar o personagem real como na minha edição anterior
drewag
agradável! É engraçado como você pode interagir com ele, mas não através de um índice. Swift está se sentindo pitônico, mas com arestas mais duras.
Doobeh
I encontrados usando os myString.bridgeToObjectiveC().characterAtIndex(0)ou (string as NSString ).characterAtIndex(0) devolve um valor Int do personagem
markhunte
4
Não use NSStringmétodos para acessar caracteres individuais de um nativo do Swift String- os dois usam mecanismos de contagem diferentes, para obter resultados imprevisíveis com caracteres Unicode mais altos. O primeiro método deve ser seguro (uma vez que os erros Unicode do Swift sejam tratados).
Nate Cozinhe
11

Como uma observação à parte, existem algumas funções aplicáveis ​​diretamente à representação da cadeia de caracteres de uma String, assim:

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

O resultado é do tipo Caractere, mas você pode convertê-lo em uma String.

Ou isto:

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

:-)

Frédéric Adda
fonte
11

Swift 4

String(Array(stringToIndex)[index]) 

Essa é provavelmente a melhor maneira de resolver esse problema uma vez. Você provavelmente deseja converter a String como uma matriz primeiro e depois converter o resultado como String novamente. Caso contrário, um caractere será retornado em vez de uma String.

O exemplo String(Array("HelloThere")[1])retornará "e" como uma String.

(Array("HelloThere")[1] retornará "e" como um personagem.

O Swift não permite que as Strings sejam indexadas como matrizes, mas isso faz o trabalho, no estilo de força bruta.

Harsh G.
fonte
1
Duplicar todo o conteúdo da string para outro local da memória é contra-desempenho, especialmente para strings grandes. Não devemos precisar de alocações extras de memória para tarefas simples, como acesso direto à memória.
Cristik
7

Minha solução muito simples:

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
fonte
Funciona no Swift 4.1
leanne
Mais simples solução e agora com Swift 5 exemplo :)
OhadM
@Linh Dao Não use encodedOffset. encodedOffset foi descontinuado: encodedOffset foi descontinuado, pois o uso mais comum está incorreto.
Leo Dabus
@OhadM mais simples não significa que esteja correto ou pelo menos não funcionará conforme o esperado Tentelet flags = "🇺🇸🇧🇷" flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
Leo Dabus 22/02
1
@OhadM Meu comentário foi apenas um aviso. Sinta-se livre para usá-lo, se você acha que se comporta como você espera.
Leo Dabus 23/02
6

Você pode fazer isso convertendo String em Array e obtendo-o por índice específico usando subscrito como abaixo

var str = "Hello"
let s = Array(str)[2]
print(s)
Soeng Saravit
fonte
1
Observe que essa solução resultará na duplicação de conteúdo, tornando-a menos eficiente quanto à memória e à CPU.
Cristik
5

Eu apenas tive o mesmo problema. Simplesmente faça o seguinte:

var aString: String = "test"
var aChar:unichar = (aString as NSString).characterAtIndex(0)
user3723247
fonte
Isso falha para muitos Emoji e outros caracteres que realmente ocupam mais de uma vez "caractere" em um NSString.
Rddy 25/17 /
5

Swift3

Você pode usar a sintaxe do subscrito para acessar o Caractere em um índice String específico.

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

ou podemos fazer uma extensão de string no 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
fonte
3

Minha solução está em uma linha, supondo que cadena seja a string e 4 seja a enésima posição que você deseja:

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

Simples ... Suponho que o Swift inclua mais coisas sobre substrings em versões futuras.

Julio César Fernández Muñoz
fonte
1
Não é o mesmo que var charAtIndex = string[advance(string.startIndex, 10)]na resposta de Sulthan?
Martin R
Sim, é a mesma solução com outro exemplo como o de Sulthan. Desculpe pela duplicata. :) É fácil duas pessoas encontrarem o mesmo caminho.
Julio César Fernández Muñoz
3

Swift 3: outra solução (testada em playground)

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
fonte
2

Atualização para substring 2.0 swift

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
fonte
2

Eu acho que uma resposta rápida para obter o primeiro personagem pode ser:

let firstCharacter = aString[aString.startIndex]

É muito elegante e com desempenho que:

let firstCharacter = Array(aString.characters).first

Mas .. se você quiser manipular e executar mais operações com strings, você poderá criar uma extensão .. existe uma extensão com essa abordagem, é bem parecida com a já postada aqui:

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

}

MAS É UMA IDEIA TERRÍVEL !!

A extensão abaixo é terrivelmente ineficiente. Toda vez que uma string é acessada com um número inteiro, uma função O (n) para avançar seu índice inicial é executada. A execução de um loop linear dentro de outro loop linear significa que esse loop for é acidentalmente O (n2) - à medida que o comprimento da string aumenta, o tempo que esse loop leva aumenta quadraticamente.

Em vez de fazer isso, você pode usar a coleção de cadeias de caracteres.

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

Dicas e truques úteis de programação (escritos por mim)

quemeful
fonte
2

Obter e definir subscrito (String e substring) - Swift 4.2

Swift 4.2, Xcode 10

Baseei minha resposta na resposta de @alecarlson . A única grande diferença é que você pode obter um Substringou um Stringretorno (e, em alguns casos, um único Character). Você também pode gete seto subscrito. Por fim, o meu é um pouco mais complicado e mais longo que a resposta de @alecarlson e, como tal, sugiro que você o coloque em um arquivo de origem.


Extensão:

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
fonte
Isso compensa desnecessariamente os dois índices (início e fim) do startIndex. Você pode simplesmente compensar o índice final usando o range.count e compensar o índice inicial
Leo Dabus
2

Swift 4.2

Essa resposta é ideal porque ela se estende Stringe todas as suas Subsequences( Substring) em uma extensão

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
fonte
Isso compensa desnecessariamente os dois índices ( starte end) do startIndex. Você pode simplesmente compensar o endíndice usando o range.count e compensar o startíndice
Leo Dabus
2

Até agora, o subscrito (_ :) está indisponível. Assim como não podemos fazer isso

str[0] 

com string.Nós precisamos fornecer "String.Index". Mas como podemos fornecer nosso próprio número de índice dessa maneira, em vez disso, podemos usar,

string[str.index(str.startIndex, offsetBy: 0)]
Yodagama
fonte
Edite sua resposta e adicione algum contexto, explicando como sua resposta resolve o problema, em vez de postar uma resposta somente de código. Da avaliação
Pedram Parsian
Por que fazer a compensação desnecessária? Por que não simplesmente string[string.startIndex]? BTW, o código não se comportaria / compilaria corretamente, pois você usava dois nomes de variáveis ​​diferentes.
Cristik
2

Swift 4.2 ou posterior

Subscrição de intervalo e intervalo parcial usando String'sindices propriedade

Como variação da resposta agradável do @LeoDabus , podemos adicionar uma extensão adicional DefaultIndicescom o objetivo de nos permitir voltar à indicespropriedade Stringao implementar os subscritos personalizados (por Intintervalos especializados e intervalos parciais) para o ú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"

Obrigado @LeoDabus por me indicar na direção de usar a indicespropriedade como uma (outra) alternativa à Stringinscrição!

dfri
fonte
1
a única desvantagem é CountableClosedRange compensará os dois índices desde o startIndex
Leo Dabus
1
@LeoDabus eu vejo. Sim, principalmente no Linux, mas não muito Swift atualmente: / Eu uso swiftenvquando o faço, mas acho que será atualizado com o 4.2 também em breve.
precisa saber é
1
@LeoDabus, obrigado por atualizar esta resposta para o Swift moderno!
dfri 27/01
1
@LeoDabus nice work! Terá que analisar os detalhes posteriormente, mas lembro que nunca gostei de ter que recorrer ao Foundation alguns dos tipos de conjuntos ordenados / contáveis.
dfri 27/01
1
obrigado parceiro!!!
Leo Dabus 27/01
2

Swift 5.1.3:

Adicione uma extensão 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"
Teja Kumar Bethina
fonte
1
Isso converterá toda a cadeia de caracteres em uma matriz de caracteres toda vez que você chamar essa propriedade para extrair um único caractere dela.
Leo Dabus
1

O Stringtipo de Swift não fornece umcharacterAtIndex método porque existem várias maneiras de codificar uma string Unicode. Você vai com UTF8, UTF16 ou algo mais?

Você pode acessar as CodeUnitcoleções recuperando as propriedades String.utf8e String.utf16. Você também pode acessar a UnicodeScalarcoleção recuperando oString.unicodeScalars propriedade.

No espírito da NSStringimplementação, estou retornando um 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
fonte
Isso falhará para caracteres que precisam de mais armazenamento que 16 bits. Basicamente, qualquer caractere Unicode além de U + FFFF.
Rddy 25/17 /
1

Para alimentar o assunto e mostrar possibilidades rápidas de subscrito, aqui está uma pequena string "substring-toolbox" baseada em subscrito

Esses métodos são seguros e nunca passam por índices de string

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
fonte
1

Uma solução semelhante a python, que permite usar índice negativo,

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

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

A propósito, pode valer a pena transpor toda a notação de fatia do python

Joseph Merdrignac
fonte
Você se importa em escrever algo que seja compilado para o Swift 4? last return ... line não parece funcionar a função advance () não está lá, eu acredito.
C0D3 11/10/1918
1

Você também pode converter String em Array of Characters assim:

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

Esta é a maneira de obter char no índice especificado em tempo constante.

O exemplo abaixo não é executado em tempo constante, mas requer tempo linear. Portanto, se você tem muita pesquisa em String por índice, use o método acima.

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