Como converter “Índice” para tipo “Int” no Swift?

96

Quero converter o índice de uma letra contida em uma string em um valor inteiro. Tentei ler os arquivos de cabeçalho, mas não consigo encontrar o tipo Index, embora pareça estar em conformidade com o protocolo ForwardIndexTypecom métodos (por exemplo distanceTo).

var letters = "abcdefg"
let index = letters.characters.indexOf("c")!

// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index)  // I want the integer value of the index (e.g. 2)

Qualquer ajuda é apreciada.

Christopher
fonte
você tem xcode 7.2 e swift 2.x?
aaisataev
Na verdade, estou baixando o Xcode 7.2 neste momento.
Christopher
33
Não há nada mais frustrante do que ver o índice que você quer encarando você em um playground e é um PITA gigante para converter o índice do tipo que você precisa. Prefiro bater meu pau na porta.
Adrian
1
Swift 3: let index = letters.characters.index(of: "c") próxima linhalet int_index = letters.characters.distance(from: letters.startIndex, to: index)
Viktor
8
Apple WTF !!!!!!
Borzh de

Respostas:

92

editar / atualizar:

Xcode 11 • Swift 5.1 ou posterior

extension StringProtocol {
    func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
    func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}

extension Collection {
    func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}

extension String.Index {
    func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}

Teste de playground

let letters = "abcdefg"

let char: Character = "c"
if let distance = letters.distance(of: char) {
    print("character \(char) was found at position #\(distance)")   // "character c was found at position #2\n"
} else {
    print("character \(char) was not found")
}

let string = "cde"
if let distance = letters.distance(of: string) {
    print("string \(string) was found at position #\(distance)")   // "string cde was found at position #2\n"
} else {
    print("string \(string) was not found")
}
Leo Dabus
fonte
49
Estou tão confuso por que eles simplesmente não decidiram fazer uma função que retorne o índice de valor inteiro de um elemento de uma matriz .. smh
MarksCode
6
Nem todos os caracteres podem ser armazenados em um único byte. Você deve tirar algum tempo e ler a documentação do Swift String
Leo Dabus
4
Tbh, estou tentando obter o índice de valor inteiro de um elemento de matriz regular, não uma string.
MarksCode de
28
Tornando as coisas simples desnecessariamente complicadas :(
Iulian Onofrei
4

Swift 4

var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2

Nota: Se String contiver os mesmos caracteres múltiplos, ele obterá apenas o mais próximo da esquerda

var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Shem Alexis Chavez
fonte
6
Não use encodedOffset. encodedOffset está obsoleto: encodedOffset tornou-se obsoleto porque o uso mais comum é incorreto. tentativa"🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16
Leo Dabus
@LeoDabus, você está declarando corretamente que ele está obsoleto. Mas você está sugerindo usá-lo como uma resposta de qualquer maneira. A resposta correta é: index_Of_YourStringVariable.utf16Offset (in: yourStringVariable)
TDesign
Não. UTF16 compensou provavelmente não é o que você quer
Leo Dabus
2

encodedOffsetfoi descontinuado do Swift 4.2 .

Mensagem de descontinuação: encodedOffsetfoi descontinuada porque o uso mais comum é incorreto. Use utf16Offset(in:)para obter o mesmo comportamento.

Então, podemos usar utf16Offset(in:)assim:

var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
Shohin
fonte
2
try let str = "🇺🇸🇺🇸🇧🇷" let index = str.firstIndex(of: "🇧🇷")?.utf16Offset(in: str)// Resultado: 8
Leo Dabus
1

Ao pesquisar um índice como este

⛔️ guard let index = (positions.firstIndex { position <= $0 }) else {

ele é tratado como Array.Index. Você tem que dar ao compilador uma pista de que você quer um inteiro

guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Milczi
fonte
0

Para executar a operação de string com base no índice, você não pode fazer isso com a abordagem numérica de índice tradicional. porque swift.index é recuperado pela função indices e não é do tipo Int. Mesmo que String seja uma matriz de caracteres, ainda não podemos ler elemento por índice.

Isso é frustrante.

Portanto, para criar uma nova substring de cada caractere par da string, verifique o código abaixo.

let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
    if i % 2 == 0 {
        resultStrArray.append(mystrArray[i])
      }
    i += 1
}
let resultString = String(resultStrArray)
print(resultString)

Resultado: acegikmoqsuwy

Desde já, obrigado

Shrikant Phadke
fonte
isso pode ser feito facilmente com o filtrovar counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }
Leo Dabus
se você preferir usar o String.indexvar index = mystr.startIndex let result = mystr.filter { _ in defer { mystr.formIndex(after: &index) } return mystr.distance(from: mystr.startIndex, to: index).isMultiple(of: 2) }
Leo Dabus
0

Esta é uma extensão que permitirá que você acesse os limites de uma substring como Ints em vez de String.Indexvalores:

import Foundation

/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
    /// Access the range of the search string as integer indices
    /// in the rendered string.
    /// - NOTE: This is "unsafe" because it may not return what you expect if
    ///     your string contains single symbols formed from multiple scalars.
    /// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
    ///     from the result of the standard function range(of:).
    func countableRange<SearchType: StringProtocol>(
        of search: SearchType,
        options: String.CompareOptions = [],
        range: Range<String.Index>? = nil,
        locale: Locale? = nil
    ) -> CountableRange<Int>? {
        guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
            return nil
        }

        let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
        let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart

        return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
    }
}

Esteja ciente de que isso pode causar estranheza, e é por isso que a Apple optou por tornar isso difícil. (Embora seja uma decisão de design discutível - esconder uma coisa perigosa apenas tornando-a difícil ...)

Você pode ler mais na documentação do String da Apple , mas o tldr é que decorre do fato de que esses "índices" são, na verdade, específicos de implementação. Eles representam os índices na string após ela ter sido processada pelo sistema operacional e, portanto, podem mudar de sistema operacional para sistema operacional, dependendo de qual versão da especificação Unicode está sendo usada. Isso significa que acessar valores por índice não é mais uma operação de tempo constante, porque a especificação UTF deve ser executada sobre os dados para determinar o local correto na string. Esses índices também não se alinham com os valores gerados por NSString, se você fizer uma ponte para ele, ou com os índices nos escalares UTF subjacentes. Aviso ao desenvolvedor.

Zack
fonte
não há necessidade de medir a distância do startIndex novamente. Basta obter a distância do inferior ao superior e adicionar o início.
Leo Dabus
Eu também adicionaria as opções ao seu método. Algo comofunc rangeInt<S: StringProtocol>(of aString: S, options: String.CompareOptions = []) -> Range<Int>? { guard let range = range(of: aString, options: options) else { return nil } let start = distance(from: startIndex, to: range.lowerBound) return start..<start+distance(from: range.lowerBound, to: range.upperBound) }
Leo Dabus
Eu provavelmente mudaria o nome do método para countableRangee retornariaCountableRange
Leo Dabus
Boas sugestões @LeoDabus em todas as áreas. Eu adicionei todos os argumentos de intervalo (de ...), na verdade, para que agora você possa chamar countableRange (de :, opções :, intervalo:, locale :). Já atualizei o Síntese, vou atualizar este post também.
Zack
Não creio que o intervalo seja necessário, considerando que você pode chamar esse método em uma substring
Leo Dabus