149

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.

8
  • do u have xcode 7.2 and swift 2.x? Commented Dec 31, 2015 at 2:28
  • 71
    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. Commented Feb 23, 2017 at 2:15
  • 4
    Swift 3: let index = letters.characters.index(of: "c") next Line let int_index = letters.characters.distance(from: letters.startIndex, to: index) Commented Jul 3, 2017 at 15:46
  • 43
    Apple WTF!!!!!! Commented Aug 15, 2017 at 19:07
  • 4
    been reading for hours. still no idea what's going on. Is this bizzarro world? Commented Mar 15, 2020 at 17:51

9 Answers 9

115

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")
}
Sign up to request clarification or add additional context in comments.

14 Comments

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.. smh
Not all characters can be stored in a single byte. You should take some time and read the Swift String documentation
Tbh I'm trying to get the integer-value index of a regular array's element, not a string.
That should be simple if your array elements conform to equatable protocol
Making simple things unnecessarily complicated :(
|
55

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

8 Comments

Thank you! Best answer! please replace "number" with "myString"
Fixed! Forgot to change that when converting.
How would you do this with lastIndex(of: ""), which also only returns that weird String.Index?
@Neph 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.
What if you're looking for "Wo" ?
|
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

3 Comments

Don't use encodedOffset. encodedOffset is deprecated: encodedOffset has been deprecated as most common usage is incorrect. try "🇺🇸🇺🇸🇧🇷".index(of: "🇧🇷")?.encodedOffset // 16
@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)
No. UTF16 offset it is probably not what you want
5

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

1 Comment

try let str = "🇺🇸🇺🇸🇧🇷" let index = str.firstIndex(of: "🇧🇷")?.utf16Offset(in: str) // Result: 8
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
}

Comments

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 {

Comments

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

2 Comments

this can be easily accomplished with filter var counter = 0 let result = mystr.filter { _ in defer { counter += 1 } return counter.isMultiple(of: 2) }
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) }
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.

6 Comments

no need to measure the distance form the startIndex again. Just get the distance from the lower to the upper and add the start.
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) }
I would probably change the method name to countableRange and return CountableRange
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.
I don't thing range is needed considering that you can call that method on a substring
|
-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
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.