¿Cómo convertir "Índice" para escribir "Int" en Swift?

90

Quiero convertir el índice de una letra contenida dentro de una cadena en un valor entero. Intenté leer los archivos de encabezado pero no puedo encontrar el tipo Index, aunque parece cumplir con el protocolo ForwardIndexTypecon los métodos (por ejemplo 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)

Se agradece cualquier ayuda.

Christopher
fuente
¿Tienes xcode 7.2 y swift 2.x?
aaisataev
De hecho, estoy descargando Xcode 7.2 en este momento.
Christopher
33
No hay nada más frustrante que ver el índice que desea mirándolo a la cara en un patio de recreo y es un PITA gigantesco para convertir el índice al tipo que necesita. Prefiero golpear mi polla en una puerta.
Adrian
1
Swift 3: let index = letters.characters.index(of: "c") next Linelet int_index = letters.characters.distance(from: letters.startIndex, to: index)
Viktor
8
¡¡¡¡WTF de Apple !!!!!!
Borzh

Respuestas:

87

editar / actualizar:

Xcode 11 • Swift 5.1 o 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) }
}

Prueba de patio de recreo

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
fuente
47
Estoy tan confundido por qué no decidirían simplemente hacer una función que devuelva el índice de valor entero del elemento de una matriz .. smh
MarksCode
6
No todos los caracteres se pueden almacenar en un solo byte. Debería tomarse un tiempo y leer la documentación de Swift String
Leo Dabus
4
Tbh, estoy tratando de obtener el índice de valor entero del elemento de una matriz regular, no una cadena.
MarksCode
28
Hacer las cosas simples innecesariamente complicadas :(
Iulian Onofrei
4

Rápido 4

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

Nota: Si String contiene los mismos caracteres múltiples, solo obtendrá el más cercano desde la izquierda

var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Shem Alexis Chávez
fuente
5
No use encodedOffset. encodedOffset está en desuso: encodedOffset ha quedado en desuso porque el uso más común es incorrecto. probar"🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16
Leo Dabus
@LeoDabus, está afirmando correctamente que está obsoleto. Pero está sugiriendo usarlo como respuesta de todos modos. La respuesta correcta es: index_Of_YourStringVariable.utf16Offset (en: yourStringVariable)
TDesign
No. Desplazamiento UTF16, probablemente no sea lo que quieres
Leo Dabus
1

encodedOffsetha quedado obsoleto de Swift 4.2 .

Mensaje de obsolescencia: encodedOffsetha quedado obsoleto porque el uso más común es incorrecto. Úselo utf16Offset(in:)para lograr el mismo comportamiento.

Entonces podemos usar utf16Offset(in:)así:

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

Para realizar una operación de cadena basada en el índice, no puede hacerlo con el enfoque numérico de índice tradicional. porque swift.index es recuperado por la función de índices y no está en el tipo Int. Aunque String es una matriz de caracteres, no podemos leer elemento por índice.

Esto es frustrante.

Entonces, para crear una nueva subcadena de cada carácter par de la cadena, verifique el código a continuación.

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)

Salida: acegikmoqsuwy

Gracias por adelantado

Shrikant Phadke
fuente
esto se puede lograr fácilmente con el filtrovar counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }
Leo Dabus
si prefiere usar 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

Aquí hay una extensión que le permitirá acceder a los límites de una subcadena como Ints en lugar 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))
    }
}

Solo tenga en cuenta que esto puede generar rarezas, razón por la cual Apple ha optado por hacerlo difícil. (Aunque esa es una decisión de diseño discutible: ocultar algo peligroso simplemente haciéndolo difícil ...)

Puede leer más en la documentación de String de Apple , pero el tldr es que se deriva del hecho de que estos "índices" son en realidad específicos de la implementación. Representan los índices en la cadena después de que el sistema operativo la haya representado, por lo que pueden cambiar de un sistema operativo a otro según la versión de la especificación Unicode que se esté utilizando. Esto significa que acceder a los valores por índice ya no es una operación de tiempo constante, porque la especificación UTF debe ejecutarse sobre los datos para determinar el lugar correcto en la cadena. Estos índices tampoco se alinearán con los valores generados por NSString, si se conecta a él, o con los índices en los escalares UTF subyacentes. Desarrollador de advertencia.

Zack
fuente
no es necesario volver a medir la distancia desde startIndex. Simplemente obtenga la distancia de la parte inferior a la superior y agregue el inicio.
Leo Dabus
También agregaría las opciones a tu 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
Probablemente cambiaría el nombre del método countableRangeCountableRange
ay
Buenas sugerencias @LeoDabus en todos los ámbitos. Agregué todo el rango (de ...) argumentos, en realidad, por lo que ahora puede llamar a counttableRange (of :, options :, range:, locale :). Han actualizado la esencia, también actualizaré esta publicación.
Zack
No creo que el rango sea necesario considerando que puedes llamar a ese método en una subcadena
Leo Dabus