Como a substring String funciona no Swift


354

Estou atualizando alguns dos meus códigos e respostas antigos com o Swift 3, mas quando cheguei ao Swift Strings e à indexação com substrings, as coisas ficaram confusas.

Especificamente, eu estava tentando o seguinte:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

onde a segunda linha estava me dando o seguinte erro

O valor do tipo 'String' não possui membro 'substringWithRange'

Vejo que Stringagora tem os seguintes métodos:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

Isso realmente me confundiu no começo, então comecei a brincar com o índice e o alcance . Esta é uma pergunta de acompanhamento e resposta para substring. Estou adicionando uma resposta abaixo para mostrar como eles são usados.


2
Para aqueles que querem obter o substring da string stackoverflow.com/q/32305891/468724
Inder Kumar Rathore

ou sequência de caracteres ou subscrito stackoverflow.com/questions/24092884/…
Leo Dabus 13/03

Respostas:


831

insira a descrição da imagem aqui

Todos os exemplos a seguir usam

var str = "Hello, playground"

Swift 4

Strings sofreu uma grande reforma no Swift 4. Quando você obtém alguma substring de um String agora, recebe um Substringtipo de volta em vez de a String. Por que é isso? Strings são tipos de valor no Swift. Isso significa que, se você usar uma String para criar uma nova, ela deverá ser copiada. Isso é bom para a estabilidade (ninguém mais vai mudar isso sem o seu conhecimento), mas é ruim para a eficiência.

Uma Substring, por outro lado, é uma referência à String original da qual ela veio. Aqui está uma imagem da documentação que ilustra isso.

Nenhuma cópia é necessária, por isso é muito mais eficiente de usar. No entanto, imagine que você tenha um Substring de dez caracteres de um milhão de caracteres. Como a Substring está referenciando a String, o sistema precisaria manter a String inteira enquanto a Substring estiver disponível. Assim, sempre que você terminar de manipular seu Substring, converta-o em uma String.

let myString = String(mySubstring)

Isso copiará apenas a substring e a memória contendo String antiga pode ser recuperada . Substrings (como um tipo) devem durar pouco.

Outra grande melhoria no Swift 4 é que Strings são coleções (novamente). Isso significa que tudo o que você pode fazer em uma coleção, você pode fazer em uma string (use subscritos, itere sobre os caracteres, filtro, etc.).

Os exemplos a seguir mostram como obter uma substring no Swift.

Obtendo substrings

Você pode obter uma substring de uma string usando subscritos ou uma série de outros métodos (por exemplo, prefix, suffix, split). Você ainda precisa usar String.Indexe não um Intíndice para o intervalo. (Veja minha outra resposta se precisar de ajuda com isso.)

Início de uma string

Você pode usar um índice (observe o intervalo unilateral do Swift 4):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

ou prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

ou ainda mais fácil:

let mySubstring = str.prefix(5) // Hello

Fim de uma string

Usando subscritos:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

ou suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

ou ainda mais fácil:

let mySubstring = str.suffix(10) // playground

Observe que, ao usar o suffix(from: index), tive que contar novamente desde o final usando -10. Isso não é necessário quando se está usando suffix(x), o que leva apenas os últimos xcaracteres de uma String.

Intervalo em uma sequência

Novamente, simplesmente usamos subscritos aqui.

let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end

let mySubstring = str[range]  // play

Convertendo SubstringparaString

Não se esqueça, quando estiver pronto para salvar sua substring, converta-a em um Stringpara que a memória da string antiga possa ser limpa.

let myString = String(mySubstring)

Usando uma Intextensão de índice?

Hesito em usar uma Intextensão de índice baseada depois de ler o artigo Strings in Swift 3 da Airspeed Velocity e Ole Begemann. Embora no Swift 4, Strings sejam coleções, a equipe do Swift propositalmente não usouIntíndices. Ainda estáString.Index. Isso tem a ver com caracteres Swift sendo compostos por números variáveis ​​de pontos de código Unicode. O índice real deve ser calculado exclusivamente para cada string.

Devo dizer que espero que a equipe Swift encontre uma maneira de abstrair String.Indexno futuro. Mas até eles eu estou escolhendo usar sua API. Isso me ajuda a lembrar que as manipulações de String não são apenas simples Intpesquisas de índice.


9
Thx pela descrição. Uprates bem merecidos. A Apple complicou demais isso. Substring deve ser tão fácil quanto string.substring [de ... para].
Teddy

Realmente boa explicação. exceto uma coisinha garbage collected;-) Espero que as pessoas aqui saibam que não há coleta de lixo no Swift.
Christian Anchor Dampf 16/01

@ChristianAnchorDampf, Obrigado por reservar um tempo para comentar. Tirei a coleta de lixo. Como está a nova redação?
Suragch 17/01

Que resposta incrível, senhor !!
davidev 20/03

194

Estou realmente frustrado com o modelo de acesso de Swift à String: tudo tem que ser um Index. Tudo o que eu quero é acessar o i-ésimo caractere da string usando Int, não o índice desajeitado e o avanço (o que acontece a cada versão principal). Então eu fiz uma extensão para String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[startIndex..<endIndex])
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play

5
Os índices são muito úteis porque um caractere pode ter mais de um byte. Tentelet str = "🇨🇭🇩🇪🇺🇸Hello" print(str.substring(to: 2))
vadian

112
Sim, entendo que um caractere (ou seja, cluster de grafema estendido ) pode ter vários bytes. Minha frustração é por que precisamos usar o método detalhado de avanço de índice para acessar os caracteres de uma string. Por que a equipe Swift não pode simplesmente adicionar algumas sobrecargas à Biblioteca Principal para abstraí-la? Se eu digitar str[5], quero acessar o caractere no índice 5, seja qual for o caractere ou quantos bytes ele leva. Swift não é tudo sobre a produtividade do desenvolvedor?
Code Different

6
@RenniePet Acredito que a Apple reconheça o problema e as mudanças estão chegando. Conforme a página do Swift Evolution no GitHub: "O Swift 4 procura tornar as strings mais poderosas e fáceis de usar, mantendo a correção Unicode por padrão". É vago, mas vamos manter nossas esperanças #
Code Different

3
@CodeDifferent Por que a Apple não adicionou acesso de caracteres subscritos? Para que as pessoas entendam que é uma coisa ruim a se fazer. Basicamente, se você fizer isso para i em 0..string.count usando subscritos que seriam de loop duplo, a causa do índice de capa precisa passar por cada byte da string para descobrir qual é o próximo caractere. Se você faz um loop usando o índice, itera sobre a string apenas uma vez. Btw, odeio isso eu mesmo, mas esse é o raciocínio por trás do subscrito não estar disponível na string in swift.
Raimundas Sakalauskas

4
@RaimundasSakalauskas esse argumento não passa por mim. O C # possui correção de Unicode e assinatura de número inteiro, o que é realmente conveniente. No Swift 1, a Apple queria que os desenvolvedores usassem countElement(str)para encontrar o comprimento. No Swift 3, a Apple criou cordas que não Sequenceestavam em conformidade e obrigou todo mundo a usar str.characters. Esses caras não têm medo de fazer alterações. Sua teimosia em inteiro subscripting em realmente difícil de entender
Código Diferente

102

Extensão Swift 5:

extension String {
    subscript(_ range: CountableRange<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
        let end = index(start, offsetBy: min(self.count - range.lowerBound, 
                                             range.upperBound - range.lowerBound))
        return String(self[start..<end])
    }

    subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
         return String(self[start...])
    }
}

Uso:

let s = "hello"
s[0..<3] // "hel"
s[3...]  // "lo"

Ou unicode:

let s = "😎🤣😋"
s[0..<1] // "😎"

2
Muito melhor, obrigado por postar esta extensão! Eu acho que, vindo do Python, o Swift é muito mais difícil do que o necessário para se acostumar. Parece que para as pessoas que estão indo na outra direção do Objetivo C para Swift, há uma confirmação mais positiva.
user3064009

11
@ Leon Acabei de removê-lo. Antes de 4.1, countestava disponível apenas emself.characters
Lou Zell

11
Existem dicas para se atentar a essa extensão específica? Por que a Apple não fez algo assim?
Andz

11
@ Andz é muito ineficiente. Começa do início da string - duas vezes - e precisa analisar todos os caracteres de lá para "range" - duas vezes.
kareman

3
Você também precisará adicionar uma extensão que requer um,CountableClosedRange<Int> se você gostaria de escrever, por exemplo s[0...2].
Chris Frederick

24

Swift 4 e 5:

extension String {
  subscript(_ i: Int) -> String {
    let idx1 = index(startIndex, offsetBy: i)
    let idx2 = index(idx1, offsetBy: 1)
    return String(self[idx1..<idx2])
  }

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

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

Como usá-lo:

"abcde" [0] -> "a"

"abcde" [0 ... 2] -> "abc"

"abcde" [2 .. <4] -> "cd"


20

Swift 4

Em rápido 4 Stringestá em conformidade com Collection. Em vez de substring, devemos agora usar um subscript.Então, se você quer cortar somente a palavra "play"de "Hello, playground", você poderia fazê-lo como este:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

É interessante saber que isso lhe dará um em Substringvez de um String. Isso é rápido e eficiente comoSubstring compartilha seu armazenamento com a String original. No entanto, o compartilhamento de memória dessa maneira também pode levar facilmente a vazamentos de memória.

É por isso que você deve copiar o resultado em uma nova String, assim que desejar limpar a String original. Você pode fazer isso usando o construtor normal:

let newString = String(result)

Você pode encontrar mais informações sobre a nova Substringclasse na [documentação da Apple]. 1 1

Portanto, se você, por exemplo, obtiver a Rangecomo resultado de uma NSRegularExpression, poderá usar a seguinte extensão:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}

Seu código falhará se range.upperBound for> length of string. Além disso, um exemplo de uso também teria sido útil, pois eu não estava familiarizado com os subscritos no Swift. Você pode incluir algo como datePartOnly = "08-01-2018: 00" [NSMakeRange (0, 10)]. Fora isso, resposta muito boa, +1 :).
dcp

hoje em dia é essa coisa estranha: text[Range( nsRange , in: text)!]
Fattie 28/01

10

Aqui está uma função que retorna a substring de uma determinada substring quando os índices inicial e final são fornecidos. Para referência completa, você pode visitar os links abaixo.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

Aqui está um link para o post do blog que eu criei para lidar com a manipulação de seqüências rapidamente. Manipulação de strings no swift (também abrange o swift 4)

Ou você pode ver essa essência no github


9

Eu tive a mesma reação inicial. Também fiquei frustrado com a forma como a sintaxe e os objetos mudam tão drasticamente em todos os principais lançamentos.

No entanto, percebi por experiência própria como sempre sofro as conseqüências de tentar combater a "mudança", como lidar com personagens de vários bytes, o que é inevitável se você estiver olhando para uma audiência global.

Por isso, decidi reconhecer e respeitar os esforços exercidos pelos engenheiros da Apple e fazer minha parte entendendo a mentalidade deles quando surgiram essa abordagem "horrível".

Em vez de criar extensões, que são apenas uma solução alternativa para facilitar sua vida (não estou dizendo que elas estejam erradas ou caras), por que não descobrir como as Strings agora são projetadas para funcionar.

Por exemplo, eu tinha esse código que estava trabalhando no Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

e depois de desistir de tentar fazer com que a mesma abordagem funcionasse, por exemplo, usando Substrings, finalmente entendi o conceito de tratar Strings como uma coleção bidirecional para a qual acabei com esta versão do mesmo código:

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

Espero que isso contribua ...


11
Bem, lidar com um problema complexo não significa que a solução possa ser elegante. Novamente, eu também entendo o problema, mas toda a classe String e lidar com isso é simplesmente horrível.
inexcitus 9/04/19

5

Mesma frustração, isso não deve ser tão difícil ...

Eu compilei este exemplo de obter posições para substring (s) de texto maior:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

retorna ("Por que", 0, 3) ("substrings", 26, 36) ("Swift3", 40, 46)


3
Isso é algum código, mas não explica realmente como a indexação e as substrings de string funcionam no swift3.
Robert

5

Eu sou novo no Swift 3, mas procurando a Stringsintaxe (index) para analogia, acho que o índice é como um "ponteiro" restrito à string e o Int pode ajudar como um objeto independente. Usando a sintaxe base + offset, podemos obter o i-ésimo caractere da string com o código abaixo:

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

Para um intervalo de caracteres (índices) de string usando a sintaxe String (range), podemos obter do i-ésésimo-quinto caracteres com o código abaixo:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

Para uma substring (range) de uma string usando String.substring (range), podemos obter a substring usando o código abaixo:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Notas:

  1. O i-ésimo e um-ésimo começam com 0.

  2. Para f-ésimo, uso offsetBY: f + 1, porque o intervalo de assinaturas usa .. <(operador semiaberto), não inclui a f-ésima posição.

  3. Obviamente, deve incluir erros de validação, como índice inválido.


5

Swift 4+

extension String {
    func take(_ n: Int) -> String {
        guard n >= 0 else {
            fatalError("n should never negative")
        }
        let index = self.index(self.startIndex, offsetBy: min(n, self.count))
        return String(self[..<index])
    }
}

Retorna uma subsequência dos primeiros n caracteres ou a sequência inteira, se a sequência for menor. (inspirado em: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html )

Exemplo:

let text = "Hello, World!"
let substring = text.take(5) //Hello

4

Eu sou um pensamento bastante mecânico. Aqui estão os princípios básicos ...

Swift 4 Swift 5

  let t = "abracadabra"

  let start1 = t.index(t.startIndex, offsetBy:0)
  let   end1 = t.index(t.endIndex, offsetBy:-5)
  let start2 = t.index(t.endIndex, offsetBy:-5)
  let   end2 = t.index(t.endIndex, offsetBy:0)

  let t2 = t[start1 ..< end1]
  let t3 = t[start2 ..< end2]                

  //or a shorter form 

  let t4 = t[..<end1]
  let t5 = t[start2...]

  print("\(t2) \(t3) \(t)")
  print("\(t4) \(t5) \(t)")

  // result:
  // abraca dabra abracadabra

O resultado é uma substring, o que significa que faz parte da string original. Para obter uma sequência separada completa, use, por exemplo,

    String(t3)
    String(t4)

Isto é o que eu uso:

    let mid = t.index(t.endIndex, offsetBy:-5)
    let firstHalf = t[..<mid]
    let secondHalf = t[mid...]

3

Swift 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o

2

Eu criei uma extensão simples para isso (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}

2

Aqui está uma implementação mais genérica:

Essa técnica ainda usa indexpara manter os padrões de Swift e implica um personagem completo.

extension String
{
    func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
    {
        return String(self[range])
    }

    func index(at: Int) -> Index
    {
        return self.index(self.startIndex, offsetBy: at)
    }
}

Para submarcar do terceiro caractere:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

Eu usei camel subStringpara indicar que retorna um Stringe não um Substring.


2

Com base no exposto, eu precisava dividir uma sequência em um caractere não imprimível, soltando o caractere não imprimível. Eu desenvolvi dois métodos:

var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]

que montei usando algumas das respostas acima.

Como uma String é uma coleção, fiz o seguinte:

var fString = String()
for (n,c) in str.enumerated(){

*if c == "\u{1A}" {
    print(fString);
    let lString = str.dropFirst(n + 1)
    print(lString)
    break
   }
 fString += String(c)
}*

O que para mim foi mais intuitivo. Qual é o melhor? Não tenho como dizer. Ambos trabalham com o Swift 5.


Obrigado pela sua resposta. Há algo de diferente em Strings no Swift 5? Ainda não tive tempo de brincar com isso.
Suragch 27/03/19

Eles dizem isso, mas não tive a chance de investigar.
Jeremy Andrews

1

Swift 4

"Substring" ( https://developer.apple.com/documentation/swift/substring ):

let greeting = "Hi there! It's nice to meet you! 👋"
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

Exemplo de extensão String:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

exemplo de uso da extensão String:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"

-1

Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
Essa variável de substring fornecerá o resultado.
Simplesmente aqui Int é convertido em Index e, em seguida, você pode dividir as strings. A menos que você obtenha erros.


2
Isto está errado. Um caractere pode consistir em um ou mais bytes. Funciona apenas com texto ascii.
Leo Dabus 13/03
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.