Get nth character of a string in Swift programming language
Solution 1
Attention: Please see Leo Dabus' answer for a proper implementation for Swift 4 and Swift 5.
Swift 4 or later
The Substring
type was introduced in Swift 4 to make substrings
faster and more efficient by sharing storage with the original string, so that's what the subscript functions should return.
Try it out here
extension StringProtocol {
subscript(offset: Int) -> Character { self[index(startIndex, offsetBy: offset)] }
subscript(range: Range<Int>) -> SubSequence {
let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
return self[startIndex..<index(startIndex, offsetBy: range.count)]
}
subscript(range: ClosedRange<Int>) -> SubSequence {
let startIndex = index(self.startIndex, offsetBy: range.lowerBound)
return self[startIndex..<index(startIndex, offsetBy: range.count)]
}
subscript(range: PartialRangeFrom<Int>) -> SubSequence { self[index(startIndex, offsetBy: range.lowerBound)...] }
subscript(range: PartialRangeThrough<Int>) -> SubSequence { self[...index(startIndex, offsetBy: range.upperBound)] }
subscript(range: PartialRangeUpTo<Int>) -> SubSequence { self[..<index(startIndex, offsetBy: range.upperBound)] }
}
To convert the Substring
into a String
, you can simply
do String(string[0..2])
, but you should only do that if
you plan to keep the substring around. Otherwise, it's more
efficient to keep it a Substring
.
It would be great if someone could figure out a good way to merge
these two extensions into one. I tried extending Note: This answer has been already edited, it is properly implemented and now works for substrings as well. Just make sure to use a valid range to avoid crashing when subscripting your StringProtocol type. For subscripting with a range that won't crash with out of range values you can use this implementationStringProtocol
without success, because the index
method does not exist there.
Why is this not built-in?
The error message says "see the documentation comment for discussion". Apple provides the following explanation in the file UnavailableStringAPIs.swift:
Subscripting strings with integers is not available.
The concept of "the
i
th character in a string" has different interpretations in different libraries and system components. The correct interpretation should be selected according to the use case and the APIs involved, soString
cannot be subscripted with an integer.Swift provides several different ways to access the character data stored inside strings.
String.utf8
is a collection of UTF-8 code units in the string. Use this API when converting the string to UTF-8. Most POSIX APIs process strings in terms of UTF-8 code units.
String.utf16
is a collection of UTF-16 code units in string. Most Cocoa and Cocoa touch APIs process strings in terms of UTF-16 code units. For example, instances ofNSRange
used withNSAttributedString
andNSRegularExpression
store substring offsets and lengths in terms of UTF-16 code units.
String.unicodeScalars
is a collection of Unicode scalars. Use this API when you are performing low-level manipulation of character data.
String.characters
is a collection of extended grapheme clusters, which are an approximation of user-perceived characters.Note that when processing strings that contain human-readable text, character-by-character processing should be avoided to the largest extent possible. Use high-level locale-sensitive Unicode algorithms instead, for example,
String.localizedStandardCompare()
,String.localizedLowercaseString
,String.localizedStandardRangeOfString()
etc.
Solution 2
Swift 5.2
let str = "abcdef"
str[1 ..< 3] // returns "bc"
str[5] // returns "f"
str[80] // returns ""
str.substring(fromIndex: 3) // returns "def"
str.substring(toIndex: str.length - 2) // returns "abcd"
You will need to add this String extension to your project (it's fully tested):
extension String {
var length: Int {
return count
}
subscript (i: Int) -> String {
return self[i ..< i + 1]
}
func substring(fromIndex: Int) -> String {
return self[min(fromIndex, length) ..< length]
}
func substring(toIndex: Int) -> String {
return self[0 ..< max(0, toIndex)]
}
subscript (r: Range<Int>) -> String {
let range = Range(uncheckedBounds: (lower: max(0, min(length, r.lowerBound)),
upper: min(length, max(0, r.upperBound))))
let start = index(startIndex, offsetBy: range.lowerBound)
let end = index(start, offsetBy: range.upperBound - range.lowerBound)
return String(self[start ..< end])
}
}
Even though Swift always had out of the box solution to this problem (without String extension, which I provided below), I still would strongly recommend using the extension. Why? Because it saved me tens of hours of painful migration from early versions of Swift, where String's syntax was changing almost every release, but all I needed to do was to update the extension's implementation as opposed to refactoring the entire project. Make your choice.
let str = "Hello, world!"
let index = str.index(str.startIndex, offsetBy: 4)
str[index] // returns Character 'o'
let endIndex = str.index(str.endIndex, offsetBy:-2)
str[index ..< endIndex] // returns String "o, worl"
String(str.suffix(from: index)) // returns String "o, world!"
String(str.prefix(upTo: index)) // returns String "Hell"
Solution 3
I just came up with this neat workaround
var firstChar = Array(string)[0]
Solution 4
Xcode 11 • Swift 5.1
You can extend StringProtocol to make the subscript available also to the substrings:
extension StringProtocol {
subscript(_ offset: Int) -> Element { self[index(startIndex, offsetBy: offset)] }
subscript(_ range: Range<Int>) -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
subscript(_ range: ClosedRange<Int>) -> SubSequence { prefix(range.lowerBound+range.count).suffix(range.count) }
subscript(_ range: PartialRangeThrough<Int>) -> SubSequence { prefix(range.upperBound.advanced(by: 1)) }
subscript(_ range: PartialRangeUpTo<Int>) -> SubSequence { prefix(range.upperBound) }
subscript(_ range: PartialRangeFrom<Int>) -> SubSequence { suffix(Swift.max(0, count-range.lowerBound)) }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
extension BidirectionalCollection {
subscript(safe offset: Int) -> Element? {
guard !isEmpty, let i = index(startIndex, offsetBy: offset, limitedBy: index(before: endIndex)) else { return nil }
return self[i]
}
}
Testing
let test = "Hello USA 🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[safe: 10] // "🇺🇸"
test[11] // "!"
test[10...] // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
test[10..<12] // "🇺🇸!"
test[10...12] // "🇺🇸!!"
test[...10] // "Hello USA 🇺🇸"
test[..<10] // "Hello USA "
test.first // "H"
test.last // "!"
// Subscripting the Substring
test[...][...3] // "Hell"
// Note that they all return a Substring of the original String.
// To create a new String from a substring
test[10...].string // "🇺🇸!!! Hello Brazil 🇧🇷!!!"
Solution 5
No indexing using integers, only using String.Index
. Mostly with linear complexity. You can also create ranges from String.Index
and get substrings using them.
Swift 3.0
let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.index(before: someString.endIndex)]
let charAtIndex = someString[someString.index(someString.startIndex, offsetBy: 10)]
let range = someString.startIndex..<someString.index(someString.startIndex, offsetBy: 10)
let substring = someString[range]
Swift 2.x
let firstChar = someString[someString.startIndex]
let lastChar = someString[someString.endIndex.predecessor()]
let charAtIndex = someString[someString.startIndex.advanceBy(10)]
let range = someString.startIndex..<someString.startIndex.advanceBy(10)
let subtring = someString[range]
Note that you can't ever use an index (or range) created from one string to another string
let index10 = someString.startIndex.advanceBy(10)
//will compile
//sometimes it will work but sometimes it will crash or result in undefined behaviour
let charFromAnotherString = anotherString[index10]
Comments
-
Mohsen over 2 years
How can I get the nth character of a string? I tried bracket(
[]
) accessor with no luck.var string = "Hello, world!" var firstChar = string[0] // Throws error
ERROR: 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
-
Doobeh almost 10 yearsYou can already loop through strings: for letter in "foo" { println(letter) }
-
drewag almost 10 years@Doobeh I meant loop through and return the actual character like in my edit above
-
Doobeh almost 10 yearsnice! It's funny how you can iterate through it, but not via an index. Swift's feeling pythonic but with harder edges.
-
markhunte almost 10 yearsI found using the
myString.bridgeToObjectiveC().characterAtIndex(0)
or(string as NSString ).characterAtIndex(0)
returns an Int value of the character -
drewag almost 10 years@markhunte That's a good point. Another argument in my mind to use the native method
-
Nate Cook almost 10 yearsDon't use
NSString
methods for accessing individual characters from a Swift-nativeString
- the two use different counting mechanisms, so you'll get unpredictable results with higher Unicode characters. The first method should be safe (once Swift's Unicode bugs are handled). -
drewag almost 10 years@NateCook you're right. I deleted the second option
-
masakielastic almost 10 yearsHow about "for (cur, char) in enumerate(self) {}"?
-
Martin R over 9 yearsIsn't that the same as
var charAtIndex = string[advance(string.startIndex, 10)]
in Sulthan's answer? -
Jeff Hay over 9 yearsThis is a good quick work-around for the (common) case where you know you have UTF8 or ASCII encoded strings. Just be sure that the strings will never be in an encoding that uses more than one byte.
-
zaph over 9 years
String
indexs are unique to a string. This is because different strings may have different multi-unit UTF-16Characters
and/or at different positions so the UTF-16 unit indexs will not match, may fall beyond the end or point inside a multi-unit UTF-16Character
. -
Sulthan over 9 years@Zaph That's obvious.
-
zaph over 9 yearsExplaning why you say: "sometimes it will crash or result in undefined behaviour". Perhaps better to say just don't do it because ...
-
Julio César Fernández Muñoz over 9 yearsYes, it's the same solution with another example like Sulthan's said. Sorry for the duplicate. :) It's easy two people found the same way.
-
Aaron Brager over 9 years@Sulthan
..
is now..<
(in your assignment torange
) -
Cajunluke over 9 yearsOn the shipping version of Swift,
string[string.endIndex]
results in EXC_BAD_INSTRUCTION apparently asstring.endIndex
is one place beyond the end of the string.string[string.endIndex-1]
doesn't work as String.Index isn't IntegerConvertible. How do we get the last character of the string? -
David L over 9 years@CajunLuke I know it's been a while since you posted this comment, but take a look at this answer. You can use
var lastChar = string[string.endIndex.predecessor()]
-
Bjorn over 9 yearsThat seems extremely inefficient as you are copying the entire string just to get the first character. Use string[string. startIndex] instead of 0 as Sulthan pointed out.
-
Mohammad Zaid Pathan about 9 yearsUnwrap string:
var firstChar = Array(string!)[0]
Otherwise it will sayadd arrayLiteral
-
mginn about 9 yearsThis should really be the accepted answer. It is efficient and clean, and works perfectly.
-
TheCodingArt almost 9 yearsI don't believe this is clean, as a matter of fact it's a round about. I'm not quite sure which initializer in Array is used first causing this to happen (and I'm assuming it's the SequenceType initializer causing it to gather the characters of the string as individual components for the Array). This isn't explicit at all and may be corrected in the future without type casting. This also doesn't work if you use shorthand for the array via [string].first. @Sulthan's solution works best to use the baked in index values. It's far more clear on what is happening here.
-
John LaBarge almost 9 yearsRight but it doesn't do the range adaptation for slicing a string that the above solution solves so elegantly.
-
Admin almost 9 years
Cannot find an overload for 'advance' that accepts an argument list of type '(String.Index, T)'
...String.Index
andInt
aren't compatible. -
aleclarson almost 9 years@Barry That's more than likely because of the Swift 2.0 release. I'm not using Swift right now, so if someone could update this answer, I'm sure people would appreciate it.
-
SoftDesigner almost 9 yearsIf you see
Cannot subscript a value of type 'String'...
check this answer: stackoverflow.com/a/31265316/649379 -
Aaron over 8 yearsI am getting
fatal error: can not increment endIndex
-
Sulthan over 8 yearsthe last operation could seriously improve performance by first getting the start index as
let start = startIndex.advancedBy(r.startIndex)
and using it as the starting point for end indexlet end = start.advanceBy(r.endIndex - r.startIndex)
. -
duthen over 8 yearsIt seems you can replace
return substringWithRange(Range(start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex)))
byreturn self[Range(start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex))]
-
jowie about 8 yearsWhen I try to use this, I get
Ambiguous use of 'subscript'
. -
Craig.Pearce about 8 years@aleclarson Looks like this will have some issues with Swift 3.
init(start:end:)
is deprecated. -
nekonari almost 8 yearsWhy are you using "==="? That's identity operator for objects not comparing values..
-
ignaciohugog almost 8 yearsWARNING! The extension below is horribly inefficient. Every time a string is accessed with an integer, an O(n) function to advance its starting index is run. Running a linear loop inside another linear loop means this for loop is accidentally O(n2) — as the length of the string increases, the time this loop takes increases quadratically. Instead of doing that you could use the characters's string collection.
-
Randel S almost 8 years@ignaciohugog Do you think String.CharacterView.Index.successor() is O(1) or O(N)? I couldn't find any O() info for .successor() or .predecessor(). Thanks.
-
Devin Brown almost 8 years
fatal error: Can't form a Character from an empty String
-
Jack almost 8 yearsCan I add this to a utilities file I am making that I eventually wish to pass to GitHub?
-
Thomas over 7 yearsI'm getting a couple errors when trying to use this with xCode 8
-
allenlinli over 7 yearsMay I ask what is "self[index(startIndex, offsetBy: i)]"? And how does "self[i]" work?
-
Frank over 7 yearsWow, compiler segment fault!
-
Ahmet Akkök over 7 yearsHi Leo, thank you for the solution! I just (today) switched to from Swift 2.3 to 3 and your solution subscript(range: Range<Int>) gives the error "Extra argument 'limitedBy' in call". What do you think can be wrong?
-
Leo Dabus over 7 years@AhmetAkkök are you sure you didn't change the code?
-
Ahmet Akkök over 7 years@Leo it turned out I did not convert the whole project but on the app not the extension, I'have repeated the process for both app and the extension and it works OK now. Your help is very much appreciated!
-
Dan Rosenstark over 7 yearsthis is very complicated code. What's the advantage over doing
return String(Array(characters)[range])
in Swift 3? -
BennX about 7 yearsFor those who get
Ambiguous use of 'subscript'
typehint to the variable type since"someString"[5]
can either return a String or a Character with the shown subscripts. You need to tell him what you need. @jowie -
Matt Le Fleur about 7 yearsThis doesn't work with Swift 3:
argument type 'String' does not conform to expected type 'Sequence'
. You would need to get theCharacterView
for the string first, then index into its array:Array(str.characters)[0]
, orString(Array(str.characters)[0])
if you want to keep the String type. -
rmaddy almost 7 yearsThis fails for many Emoji and other characters that actually take up more than once "character" in an
NSString
. -
rmaddy almost 7 yearsThis will fail for characters that need more storage than 16 bits. Basically, any Unicode character beyond U+FFFF.
-
Leo Dabus over 6 yearsthe only disadvantage is CountableClosedRange will offset both indexes from the startIndex
-
Leo Dabus over 6 yearschange
range.upperBound - range.lowerBound
torange.count
-
Chris Prince over 6 yearsIt's not part of the original question, but... it would be nice if this supported assignment too. E.g., s[i] = "a" :).
-
Leo Dabus about 6 yearsstackoverflow.com/a/38215613/4573247 now extends StringProtocol to support Substrings as well
-
Noah Wilder about 6 yearsWhy did you choose to return
SubSequence
rather than a value likeSubString
orString
? -
Leo Dabus about 6 yearsI am extending the protocol not the type (String) so you need to return SubSequence which in the case of a String is a Substring.
-
mfaani about 6 yearsI'm confused. So is there any case where the Swift3 answer won't work? If not what Apple did makes no sense
-
Noah Wilder about 6 yearsCheck out my answer, it uses the same thing (credit to @alecarlson), but you can
get
andset
the subscript. -
leanne over 5 yearsWorks in Swift 4.1
-
Leo Dabus over 5 yearsThis method as it is unnecessarily offsetting both bounds from the startIndex. What you should do to avoid that is offset the endIndex starting from the start (not the startIndex) offsetBy the range count. Regarding the StringProtocol subscript extension I have already posted it.
-
Leo Dabus over 5 yearsSwift 4.2
DefaultBidirectionalIndices
has been renamed toDefaultIndices
-
aleclarson over 5 years@LeoDabus I edited my answer to recommend yours. Good work!
-
Leo Dabus over 5 years@aleclarson Just change startIndex to start and upperBound to count when defining the end index. This is probably the reason of 20 downvotes. Also cast Character to Character in your Swift 3 version is pointless
-
aleclarson over 5 years@LeoDabus Feel free to make those edits yourself instead of telling me to do so. :)
-
Leo Dabus over 5 yearsNot yet. May be in 10-14 days from now. Are you coding on linux?
-
dfrib over 5 years@LeoDabus I see. Yes mostly linux but not much Swift these days :/ I use
swiftenv
when I do, though, I guess it will be updated with 4.2 also somewhat soon then. -
C0D3 over 5 yearsDo you mind writing something that compiles for Swift 4? last return ... line doesn't seem to work advance() function is not there I believe.
-
C0D3 over 5 yearsI believe with Swift 4.2 subscripts are not available again. I get an error saying: 'subscript' is unavailable: cannot subscript String with an Int, see the documentation comment for discussion
-
Leo Dabus over 5 years@ChrisPrince
extension StringProtocol where Self: RangeReplaceableCollection { subscript(offset: Int) -> Element { get { return self[index(startIndex, offsetBy: offset)] } set { let start = index(startIndex, offsetBy: offset) replaceSubrange(start..<index(after: start), with: [newValue]) } } }
-
Leo Dabus over 5 yearsThis is unnecessarily offsets both indexes (
start
andend
) from thestartIndex
. You could simply offset theend
index using the range.count and offset thestart
index -
Michael over 5 years@ignaciohugog wait. I think it ends up being more like O(n!)
-
Eric Aya about 5 yearsSame solution already given by Sreekanth G, Matt Le Fleur and others. And your version is worse because it doesn't convert back the result to a string.
-
bibble triple about 5 yearsbut it doesnt have to thats for the person to decide what they want to do with the result
-
Eric Aya about 5 yearsI understand what you say. But the second part of my comment was just an addition to the first part, which was the real important part: this solution has already been given in other answers. Please don't post what has already been posted. Don't post duplicate content, that's all I'm saying.
puts each letters of your string into arrrays
is already the solution given by Sreekanth G and others. Literally the same. -
OhadM over 4 yearsSimplest solution and now with Swift 5 example :)
-
Pedram over 4 yearsPlease edit your answer and add some context by explaining how your answer solves the problem, instead of posting code-only answer. From Review
-
Cristik over 4 yearsWhy doing the unnecessary offset-ing? Why not simply
string[string.startIndex]
? BTW, the code would not correctly behave/compile, as you used two different variable names. -
Cristik over 4 yearsShouldn't a method named
retrieveFirstCharacter
return aCharacter
? -
Cristik over 4 yearsNote that this solution will result in contents duplication, making it less performant memory wise and CPU-wise.
-
Cristik over 4 yearsFor a simple task like this one, the solution poses performance issues, as the whole contents of the string will be duplicated in memory.
-
Cristik over 4 yearsDuplicating the whole string contents to another memory location is counter-performant, especially for large strings. We should not need extra memory allocations for simple tasks like direct memory access.
-
Cristik over 4 yearsCreating an
Array
just to access an element seems like over-engineering, and also has performance costs as the string contents will have to be duplicated in memory. -
Emre Değirmenci over 4 yearsI want to use Java
charAt(i)
usage in my Swift string traverse within for loop without writing extension or another loop. How can I do ? -
dfrib over 4 years@LeoDabus thanks for updating this answer to modern Swift!
-
Leo Dabus over 4 yearsI think you will like this one stackoverflow.com/questions/59887561/…
-
dfrib over 4 years@LeoDabus nice work! Will have to look into the details at a later time, but I do recall that I never liked that we had to fall back on Foundation some of the ordered/countable set types.
-
Ammar Mujeeb over 4 yearsThis should be built-in functions
-
James Wang over 4 yearsWhy Swift make string so hard to use? Only Swift support Unicode? I think modern languages such Java, Javascript should all support Unicode, why their String is way easier to use?
-
James Wang over 4 yearsMost time user use short string , so efficiency is not an issue at all. Swift shouldn't make Swift String so hard to use, just see How Java String handle Unicode so user-friendly
-
nalexn over 4 years@JamesWang this is done for compatibility with different String character systems. Characters may take a different number of bytes, based on encoding, and there is no simple solution if you want both convenience of the API and best performance and memory consumption.
-
Leo Dabus about 4 yearsThis will convert the whole string into an array of characters every time you call this property to extract a single character from it.
-
Leo Dabus about 4 years@Linh Dao Don't use encodedOffset. encodedOffset is deprecated: encodedOffset has been deprecated as most common usage is incorrect.
-
Leo Dabus about 4 years@OhadM simplest doesn't mean it is correct or at least it won't work as you expect Try
let flags = "🇺🇸🇧🇷"
flags[String.Index(utf16Offset: 4, in: flags)] // "🇧🇷"
-
Leo Dabus about 4 yearsThis is unnecessarily offsets both indexes (start and end) from the startIndex. You could simply offset the end index using the range.count and offset the start index
-
OhadM about 4 years@LeoDabus, There are many solutions for a question and each solution solves it differently and can help other devs for other problems.
-
Leo Dabus about 4 years@OhadM My comment was just a warning. Fell free to use it if you think that it behaves how you expect.
-
Tilak Madichetti about 4 yearsWhatever you guys discussing it's an O(n) operation, just because it works doesn't mean you should use it . But it really depends on how frequently you are accessing the first character and how long really the string is. Anyways I wanted to throw it out there in case anyone is wondering or needs help .
-
iaomw almost 4 yearsThat looks like O(n).
-
Abhishek Binniwale over 3 yearsThis trick worked for me too . I am converting character into string back . Swift5 : var stringAtIndex = String(Array(inputString)[index])
-
Peter Schorn over 3 years
i ..< i + 1
should be changed toi...i
. -
Surjeet Rajput about 3 yearswhat will be the time complexity of doing this ?
-
multitudes about 3 yearsThe time complexity is the same as doing it in code with
let firstChar = string[string.index(string.startIndex, offsetBy: 0)]
-
Leo Dabus almost 3 yearsThis is exactly what is being done at the accepted answer. The only difference is that you are extending String instead of the StringProtocol (which supports substrings as well) and returning a string instead of a character which is totally unexpected considering that it should return the collection element which in this case is a character
-
Eric about 2 yearsAlso
let stringArray: [String] = myString { String($0) }; stringArray[0]
works too.