How to convert "Index" to type "Int" in Swift?
Asked Answered
B

9

144

I want to convert the index of a letter contained within a string to an integer value. Attempted to read the header files but I cannot find the type for Index, although it appears to conform to protocol ForwardIndexType with methods (e.g. 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)

Any help is appreciated.

Buskined answered 31/12, 2015 at 2:25 Comment(8)
do u have xcode 7.2 and swift 2.x?Alberto
Actually, I am downloading Xcode 7.2 right at this moment.Buskined
For unique characters string: let index = String(letters.characters.reverse()).characters.indexOf("c")!.distanceTo(letters.endIndex)Journalese
There's nothing more frustrating than seeing the index you want staring you in the face in a playground and it's a gigantic PITA to convert the index to the kind you need.Gilbertegilbertian
Swift 3: let index = letters.characters.index(of: "c") next Line let int_index = letters.characters.distance(from: letters.startIndex, to: index)Adytum
Apple WTF!!!!!!Concatenation
please see github.com/frogcjn/…Timeconsuming
been reading for hours. still no idea what's going on. Is this bizzarro world?Unclear
S
113

edit/update:

Xcode 11 • Swift 5.1 or later

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

Playground testing

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")
}
Schock answered 31/12, 2015 at 2:46 Comment(14)
I'm so confused why they wouldn't just decide to make a function that returns the integer-value index of an array's element.. smhPalazzo
Not all characters can be stored in a single byte. You should take some time and read the Swift String documentationSchock
developer.apple.com/library/content/documentation/Swift/…Schock
Tbh I'm trying to get the integer-value index of a regular array's element, not a string.Palazzo
That should be simple if your array elements conform to equatable protocolSchock
I'd think so, but I keep getting an Array.index instead of an int when trying to get the index of a string in an array of strings: imgur.com/a/IaaHnPalazzo
Making simple things unnecessarily complicated :(Additive
Please see github.com/frogcjn/…Timeconsuming
encodedOffset is going to be deprecated, compare github.com/apple/swift-evolution/blob/master/proposals/….Discounter
It looks kinda complicated to me, but I am a noob. I wrote this gist.github.com/psksvp/bb84c28bee8753f4d0af791d79bf281dRubeola
This is so complicated (awful) and yet there doesn't seem to be any other simpler answers, it helped me to decide to refactor my code and go a completely different route. So thx.Plutocracy
@JohnPitts The intent of this post is to add functionality to the language, not to teach you how to code. All you need to do is to add those extensions to your project. If you have a question about the code itself feel free to open a new question.Schock
If you cast your String to an NSString, you can access the older, simpler APIs for numerical indexing, but your code will absolutely have bugs or break if it has to interact with UTF sequences where multiple characters combine into a single symbol. As @LeoDabus suggests, see the Swift String docs for more info on this issue. Or, if you want to pluck your eyes out, try reading the UTF spec...Fieldfare
Thanks so much, it's really disappointing that this still isn't possible in vanilla Swift in 2023! For people who want it to work like in Java (-1 instead of nil if the element/substring isn't found), simply do: 1:let r = range(of: string)?.lowerBound.distance(in: self), 2:if r==nil { return -1 }, 3:return r! and return a regular Int (instead of the optional Int?).Spic
N
50

Works for Xcode 13 and Swift 5

let myString = "Hello World"

if let i = myString.firstIndex(of: "o") {
  let index: Int = myString.distance(from: myString.startIndex, to: i)
  print(index) // Prints 4
}

The function func distance(from start: String.Index, to end: String.Index) -> String.IndexDistance returns an IndexDistance which is just a typealias for Int

Noncontributory answered 5/2, 2021 at 6:42 Comment(8)
Thank you! Best answer! please replace "number" with "myString"Strickler
Fixed! Forgot to change that when converting.Noncontributory
How would you do this with lastIndex(of: ""), which also only returns that weird String.Index?Spic
@Spic It would literally be the exact same thing except you use lastIndex(of: ) on line 3 in place of firstIndex(of: )Noncontributory
@Noncontributory Thanks for your reply! Tbh, I'm still not sure what this actually does but it works (don't forget to also count the space while double-checking the result...)! No idea why Apple doesn't just do it that way by default!Spic
@Spic All it does is gets the first index (or last in your case) of the letter and stores it in the constant i. Then it calculates the distance from the string's starting index to i. Those are all built in methods that I used which Swift provides.Noncontributory
Naming the distance index is misleading. You can NOT use it to subscript a string. BTW Swift is a type inferred language. No need to explicitly set the resulting type.Schock
What if you're looking for "Wo" ?Dayledaylight
L
9

Swift 4

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

Note: If String contains same multiple characters, it will just get the nearest one from left

var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Lavella answered 19/9, 2018 at 3:2 Comment(3)
Don't use encodedOffset. encodedOffset is deprecated: encodedOffset has been deprecated as most common usage is incorrect. try "🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16Schock
@LeoDabus you are correctly stating it is deprecated. But you are suggesting using it as an answer anyways. The correct answer is: index_Of_YourStringVariable.utf16Offset(in: yourStringVariable)Gyrfalcon
No. UTF16 offset it is probably not what you wantSchock
G
5

Swift 5

You can do convert to array of characters and then use advanced(by:) to convert to integer.

let myString = "Hello World"

if let i = Array(myString).firstIndex(of: "o") {
  let index: Int = i.advanced(by: 0)
  print(index) // Prints 4
}
Ganesa answered 16/3, 2022 at 3:24 Comment(0)
B
4

encodedOffset has deprecated from Swift 4.2.

Deprecation message: encodedOffset has been deprecated as most common usage is incorrect. Use utf16Offset(in:) to achieve the same behavior.

So we can use utf16Offset(in:) like this:

var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
Burrows answered 3/6, 2020 at 8:0 Comment(1)
try let str = "🇺🇸🇺🇸🇧🇷" let index = str.firstIndex(of: "🇧🇷")?.utf16Offset(in: str) // Result: 8Schock
R
3

When searching for index like this

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

it is treated as Array.Index. You have to give compiler a clue you want an integer

✅ guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Retain answered 29/11, 2020 at 22:8 Comment(0)
V
0

To perform string operation based on index , you can not do it with traditional index numeric approach. because swift.index is retrieved by the indices function and it is not in the Int type. Even though String is an array of characters, still we can't read element by index.

This is frustrating.

So ,to create new substring of every even character of string , check below code.

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)

Output : acegikmoqsuwy

Thanks In advance

Violetvioleta answered 10/5, 2020 at 11:29 Comment(2)
this can be easily accomplished with filter var counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }Schock
if you prefer using the String.index var index = mystr.startIndex let result = mystr.filter { _ in defer { mystr.formIndex(after: &index) } return mystr.distance(from: mystr.startIndex, to: index).isMultiple(of: 2) }Schock
F
0

Here is an extension that will let you access the bounds of a substring as Ints instead of String.Index values:

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

Just be aware that this can lead to weirdness, which is why Apple has chosen to make it hard. (Though that's a debatable design decision - hiding a dangerous thing by just making it hard...)

You can read more in the String documentation from Apple, but the tldr is that it stems from the fact that these "indices" are actually implementation-specific. They represent the indices into the string after it has been rendered by the OS, and so can shift from OS-to-OS depending on what version of the Unicode spec is being used. This means that accessing values by index is no longer a constant-time operation, because the UTF spec has to be run over the data to determine the right place in the string. These indices will also not line up with the values generated by NSString, if you bridge to it, or with the indices into the underlying UTF scalars. Caveat developer.

Fieldfare answered 30/8, 2020 at 17:31 Comment(6)
no need to measure the distance form the startIndex again. Just get the distance from the lower to the upper and add the start.Schock
I would also add the options to your method. Something like func 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) }Schock
I would probably change the method name to countableRange and return CountableRangeSchock
Good suggestions @LeoDabus all across the board. I added all the range(of...) arguments, actually, so you can now call countableRange(of:, options:, range:,locale:). Have updated the Gist, will update this post too.Fieldfare
I don't thing range is needed considering that you can call that method on a substringSchock
I agree that it's possible to get the functionality without using that argument, but I still think it's better to match the Apple-provided API as closely as I can there.Fieldfare
A
-1

In case you got an "index is out of bounds" error. You may try this approach. Working in Swift 5

extension String{

   func countIndex(_ char:Character) -> Int{
        var count = 0
        var temp = self
  
        for c in self{
            
            if c == char {
               
                //temp.remove(at: temp.index(temp.startIndex,offsetBy:count))
                //temp.insert(".", at: temp.index(temp.startIndex,offsetBy: count))
                
                return count

            }
            count += 1
         }
        return -1
    }
}
Andee answered 20/10, 2021 at 3:35 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.