Jak przekonwertować „Index” na „Int” w Swift?

90

Chcę przekonwertować indeks litery zawartej w ciągu na wartość całkowitą. Podjęto próbę odczytania plików nagłówkowych, ale nie mogę znaleźć typu dla Index, chociaż wydaje się, że jest zgodny z protokołem ForwardIndexTypez metodami (np 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)

Każda pomoc jest mile widziana.

Krzysztof
źródło
czy masz xcode 7.2 i swift 2.x?
aaisataev
Właściwie pobieram Xcode 7.2 w tej chwili.
Christopher
33
Nie ma nic bardziej frustrującego niż widok indeksu, który chcesz, patrząc ci w twarz na placu zabaw, a konwersja indeksu na taki, jakiego potrzebujesz, jest gigantycznym PITA. Wolałbym raczej zatrzasnąć mojego dzięcioła drzwiami.
Adrian
1
Swift 3: let index = letters.characters.index(of: "c") następna linialet int_index = letters.characters.distance(from: letters.startIndex, to: index)
Viktor
8
Apple WTF !!!!!!
Borzh

Odpowiedzi:

87

edytuj / aktualizuj:

Xcode 11 • Swift 5.1 lub nowszy

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

Testy na placach zabaw

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
źródło
47
Jestem tak zdezorientowany, dlaczego nie zdecydowaliby się po prostu utworzyć funkcji zwracającej indeks wartości całkowitej elementu tablicy.
Smh
6
Nie wszystkie znaki można przechowywać w jednym bajcie. Powinieneś poświęcić trochę czasu i przeczytać dokumentację Swift String
Leo Dabus
4
Tbh Próbuję uzyskać indeks wartości całkowitej elementu zwykłej tablicy, a nie ciąg.
MarksCode
28
Sprawianie, że proste rzeczy niepotrzebnie komplikują :(
Iulian Onofrei
4

Szybki 4

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

Uwaga: jeśli Ciąg zawiera te same wiele znaków, po prostu pobierze najbliższy od lewej

var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Shem Alexis Chavez
źródło
5
Nie używaj encodedOffset. encodedOffset jest przestarzały: encodedOffset jest przestarzały, ponieważ większość typowych zastosowań jest niepoprawna. spróbuj"🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16
Leo Dabus
@LeoDabus poprawnie stwierdzasz, że jest przestarzały. Ale i tak sugerujesz użycie go jako odpowiedzi. Prawidłowa odpowiedź to: index_Of_YourStringVariable.utf16Offset (in: yourStringVariable)
TDesign
Nie. Offset UTF16 prawdopodobnie nie jest tym, czego chcesz
Leo Dabus
1

encodedOffsetwycofał się z wersji Swift 4.2 .

Komunikat o wycofaniu : encodedOffsetzostał wycofany, ponieważ najczęstsze użycie jest nieprawidłowe. Użyj, utf16Offset(in:)aby osiągnąć to samo zachowanie.

Więc możemy użyć w utf16Offset(in:)ten sposób:

var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
Shohin
źródło
1
spróbuj let str = "🇺🇸🇺🇸🇧🇷" let index = str.firstIndex(of: "🇧🇷")?.utf16Offset(in: str)// Wynik: 8
Leo Dabus
0

Aby wykonać operację na łańcuchach w oparciu o indeks, nie można tego zrobić tradycyjnym podejściem numerycznym indeksu. ponieważ swift.index jest pobierany przez funkcję indeksów i nie jest typu Int. Mimo że String jest tablicą znaków, nadal nie możemy odczytać elementu po indeksie.

To jest frustrujące.

Tak więc, aby utworzyć nowy podciąg każdego parzystego znaku ciągu, sprawdź poniższy kod.

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)

Wyjście: acegikmoqsuwy

Z góry dziękuję

Shrikant Phadke
źródło
można to łatwo osiągnąć za pomocą filtravar counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }
Leo Dabus
jeśli wolisz używać 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

Oto rozszerzenie , które pozwoli ci uzyskać dostęp do granic podciągu jako Ints zamiast String.Indexwartości:

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

Miej tylko świadomość, że może to prowadzić do dziwności, dlatego Apple zdecydowało się to utrudnić. (Chociaż jest to dyskusyjna decyzja projektowa - ukrywanie niebezpiecznej rzeczy tylko przez utrudnianie jej ...)

Możesz przeczytać więcej w dokumentacji String firmy Apple , ale tldr wynika z faktu, że te „indeksy” są w rzeczywistości specyficzne dla implementacji. Reprezentują one indeksy w ciągu znaków po wyrenderowaniu go przez system operacyjny , więc mogą przechodzić z systemu operacyjnego do systemu operacyjnego w zależności od używanej wersji specyfikacji Unicode. Oznacza to, że dostęp do wartości według indeksu nie jest już operacją działającą w czasie stałym, ponieważ specyfikacja UTF musi zostać przeprowadzona na danych, aby określić właściwe miejsce w ciągu. Te indeksy również nie będą zgodne z wartościami generowanymi przez NSString, jeśli połączysz się z nim, lub z indeksami do bazowych skalarów UTF. Caveat developer.

Zack
źródło
nie ma potrzeby ponownego mierzenia odległości od startIndex. Po prostu uzyskaj odległość od dołu do góry i dodaj początek.
Leo Dabus
Dodałbym również opcje do twojej metody. Coś w rodzajufunc 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
Prawdopodobnie zmieniłbym nazwę metody na countableRangei wróciłbymCountableRange
Leo Dabus
Dobre sugestie @LeoDabus na całej planszy. Dodałem wszystkie argumenty range (of ...), więc możesz teraz wywołać countableRange (of :, options :, range:, locale :). Zaktualizowałem Gist, zaktualizuję również ten post.
Zack
Nie sądzę, żeby zasięg był potrzebny, biorąc pod uwagę, że można wywołać tę metodę na podciągu
Leo Dabus