Random replace using Swift - swift

I am experiencing a problem that I am not sure how to solve and I hope someone here can help me. Currently I have a string variable and later I replace the letters in the string with underscores like the following:
var str = "Hello playground"
let replace = str.replacingOccurrences(of: "\\S", with: "_", options: .regularExpression)
print(str)
Know I would like to randomly generate 25 % of the characters in str (In this case 16 * 0,25 = 4) so it later prints something like these examples:
str = "H__l_ ___yg_____"
str = "_____ play______"
str = "__ll_ ____g____d"
Does anyone have any ideas of how to do this?

A possible solution:
var str = "Hello playground"
print("Before: \(str)")
do {
let regex = try NSRegularExpression(pattern: "\\S", options: [])
let matches = regex.matches(in: str, options: [], range: NSRange(location: 0, length: str.utf16.count))
//Retrieve 1/4 elements of the string
let randomElementsToReplace = matches.shuffled().dropLast(matches.count * 1/4)
matches.forEach({ (aMatch) in
if randomElementsToReplace.first(where: { $0.range == aMatch.range } ) != nil {
str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_")
} else {
//Do nothing because that's the one we are keeping as such
}
})
print("After: \(str)")
} catch {
print("Error while creating regex: \(error)")
}
The idea behind it:
Use the same Regular Expression pattern as the one you used.
Pick up n elements in it (in your case 1/4)
Replace every character that isn't in that short list.
Now that you got the idea, it's even faster replacing the for loop with
for aMatch in randomElementsToReplace {
str.replaceSubrange(Range(aMatch.range, in: str)!, with: "_")
}
Thanks to #Martin R's comment for pointing it out.
Output (done 10 times):
$>Before: Hello playground
$>After: ____o ___y____n_
$>Before: Hello playground
$>After: _el__ _______u__
$>Before: Hello playground
$>After: _e___ ____g___n_
$>Before: Hello playground
$>After: H___o __a_______
$>Before: Hello playground
$>After: H___o _______u__
$>Before: Hello playground
$>After: __l__ _____ro___
$>Before: Hello playground
$>After: H____ p________d
$>Before: Hello playground
$>After: H_l__ _l________
$>Before: Hello playground
$>After: _____ p____r__n_
$>Before: Hello playground
$>After: H___o _____r____
$>Before: Hello playground
$>After: __l__ ___y____n_
You'll see that there is a little difference from your expected result, it's because matches.count == 15, so 1/4 of them should be what? It's up to you there to do the correct calculation according to your needs (round up?, etc.) since you didn't specified it.
Note that if you don't want to round up, you could also do the reverse, use the randomed for the one to not replace, and then the round might play in your favor.

Similarly as in Replace specific characters in string, you can map each character, and combine the result to a string. But now you have to keep track of the (remaining) numbers of non-space characters, and the (remaining) numbers of characters that should be displayed. For each (non-space) character it is randomly decided whether to display (keep) it or to replace it by an underscore.
let s = "Hello playground"
let factor = 0.25
var n = s.filter({ $0 != " " }).count // # of non-space characters
var m = lrint(factor * Double(n)) // # of characters to display
let t = String(s.map { c -> Character in
if c == " " {
// Preserve space
return " "
} else if Int.random(in: 0..<n) < m {
// Keep
m -= 1
n -= 1
return c
} else {
// Replace
n -= 1
return "_"
}
})
print(t) // _e_l_ ______o_n_

This method creates an array of bools that determines which characters will be kept and which will be replaced by using the inbuilt shuffled function.
let string = "Hello playground"
let charsToKeep = string.count / 4
let bools = (Array<Bool>(repeating: true, count: charsToKeep)
+ Array<Bool>(repeating: false, count: string.count - charsToKeep)).shuffled()
let output = zip(string, bools).map
{
char, bool in
return bool ? char : "_"
}
print(String(output))
Edit The above doesn't deal with spaces correctly, but I'll leave it here anyway as a general example.
Here is a version that does deal with the spaces.
let string = "Hello playground and stackoverflow"
let nonSpaces = string.filter{ $0 != " " }.count
let bools = (Array<Bool>(repeating: true, count: nonSpaces / 4) + Array<Bool>(repeating: false, count: nonSpaces - nonSpaces / 4)).shuffled()
var nextBool = bools.makeIterator()
let output = string.map
{
char in
return char == " " ? " " : (nextBool.next()! ? char : "_")
}
print(String(output))
// Hel__ __________ a__ __a____e____w
// ___l_ _l__g_____ _n_ __a_____r__o_

Another possible approach is to generate random indexes for the given string and then replace the characters at those indexes:
var str = "Hello, playground"
let indexes: [Int] = Array(0..<str.count)
let randomIndexes = Array(indexes.shuffled()[0..<(str.count / 4)])
for index in randomIndexes {
let start = str.index(str.startIndex, offsetBy: index)
let end = str.index(str.startIndex, offsetBy: index+1)
str.replaceSubrange(start..<end, with: "_")
}
print(str)
If you put this in a extension on String, it would look like:
extension String {
func randomUnderscores(factor: Double) -> String {
let indexes: [Int] = Array(0..<count)
let endIndexes = Int(Double(count) * factor)
let randomIndexes = Array(indexes.shuffled()[0..<endIndexes])
var randomized = self
for index in randomIndexes {
let start = randomized.index(startIndex, offsetBy: index)
let end = randomized.index(startIndex, offsetBy: index+1)
randomized.replaceSubrange(start..<end, with: "_")
}
return randomized
}
}
print(str.randomUnderscores(factor: 0.25))

I just came up with the following solution:
func generateMyString(string: String) -> String {
let percentage = 0.25
let numberOfCharsToReplace = Int(floor(Double(string.count) * percentage))
let generatedString = stride(from: 0, to: string.count, by: 1).map { index -> String in
return string[string.index(string.startIndex, offsetBy: index)] == " " ? " " : "_"
}.joined()
var newString = generatedString
for i in generateNumbers(repetitions: numberOfCharsToReplace, maxValue: string.count - 1) {
var newStringArray = Array(newString)
newStringArray[i] = Array(string)[i]
newString = String(newStringArray)
}
return newString
}
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions for the numbers to be unique")
}
var numbers = [Int]()
for _ in 0..<repetitions {
var n: Int
repeat {
n = Int.random(in: 1...maxValue)
} while numbers.contains(n)
numbers.append(n)
}
return numbers
}
Output:
let str = "Hello playground"
print(generateMyString(string: str)) // ___lo _l_______d

A solution that keeps whitespaces and punctation intact.
We will find them with an extension method indiciesOfPuntationBlanks() -> [Int]. replacing the randomly picked chars will be done by blankOut(percentage: Double) -> String
extension String {
func indiciesOfPuntationBlanks() -> [Int] {
let charSet = CharacterSet.punctuationCharacters.union(.whitespaces)
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.rangeOfCharacter(from: charSet, options: [], range: searchStartIndex ..< self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func blankOut(percentage: Double) -> String {
var result = self
let blankIndicies = result.indiciesOfPuntationBlanks()
let allNonBlankIndicies = Set(0 ..< result.count).subtracting(blankIndicies).shuffled()
let picked = allNonBlankIndicies.prefix(Int(Double(allNonBlankIndicies.count) * percentage))
picked.forEach { (idx) in
let start = result.index(result.startIndex, offsetBy: idx);
let end = result.index(result.startIndex, offsetBy: idx + 1);
result.replaceSubrange(start ..< end, with: "_")
}
return result
}
}
Usage:
let str = "Hello, World!"
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75))
}
Output:
____o, _or__!
_e___, __rl_!
_e__o, __r__!
H____, W_r__!
H_l__, W____!
_____, _or_d!
_e_lo, _____!
_____, _orl_!
_____, _or_d!
___l_, W___d!
Same solution but the string for blanking out and the character sets to be ignored can be configured
extension String {
func indicies(with charSets:[CharacterSet]) -> [Int] {
var indices = [Int]()
let combinedCahrSet: CharacterSet = charSets.reduce(.init()) { $0.union($1) }
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.rangeOfCharacter(from: combinedCahrSet, options: [], range: searchStartIndex ..< self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func blankOut(percentage: Double, with blankOutString: String = "_", ignore charSets: [CharacterSet] = [.punctuationCharacters, .whitespaces]) -> String {
var result = self
let blankIndicies = result.indicies(with: charSets)
let allNonBlankIndicies = Set(0 ..< result.count).subtracting(blankIndicies).shuffled()
let picked = allNonBlankIndicies.prefix(Int(Double(allNonBlankIndicies.count) * percentage))
picked.forEach { (idx) in
let start = result.index(result.startIndex, offsetBy: idx);
let end = result.index(result.startIndex, offsetBy: idx + 1);
result.replaceSubrange(start ..< end, with: blankOutString)
}
return result
}
}
Usage:
let str = "Hello, World!"
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75))
}
print("--------------------")
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75, with:"x", ignore: [.punctuationCharacters]))
}
print("--------------------")
for _ in 0 ..< 10 {
print(str.blankOut(percentage: 0.75, with:"*", ignore: []))
}
Output:
_el_o, _____!
__llo, _____!
He__o, _____!
_e___, W_r__!
_el_o, _____!
_el__, ___l_!
_e___, __rl_!
_e__o, _o___!
H____, Wo___!
H____, __rl_!
--------------------
xxxlx,xWxrxx!
xxxxx,xxorxd!
Hxxxx,xWxrxx!
xxxxx, xoxlx!
Hxllx,xxxxxx!
xelxx,xxoxxx!
Hxxxx,xWxxxd!
Hxxxo,xxxxxd!
Hxxxx,xxorxx!
Hxxxx, Wxxxx!
--------------------
***l***Wo**d*
*e**o**W**l**
***lo**Wo****
*el*****or***
H****,****ld*
***l*, **r***
*el*o* ******
*e*lo*******!
H*l****W***d*
H****, **r***

You can use a 3-steps algorithm that does the following:
builds the list of all non-space indices
removes the first 25% random elements from that list
go through all characters and replace all whose index is part of list from #2, by an underscore
The code could look something like this:
func underscorize(_ str: String, factor: Double) -> String {
// making sure we have a factor between 0 and 1
let factor = max(0, min(1, factor))
let nonSpaceIndices = str.enumerated().compactMap { $0.1 == " " ? nil : $0.0 }
let replaceIndices = nonSpaceIndices.shuffled().dropFirst(Int(Double(str.count) * factor))
return String(str.enumerated().map { replaceIndices.contains($0.0) ? "_" : $0.1 })
}
let str = "Hello playground"
print(underscorize(str, factor: 0.25))
Sample results:
____o p_ay______
____o p__y____n_
_el_o p_________

The idea is same as above methods, just with a little less code.
var str = "Hello playground"
print(randomString(str))
print(randomString(str))
// counting whitespace as a random factor
func randomString(_ str: String) -> String{
let strlen = str.count
let effectiveCount = Int(Double(strlen) * 0.25)
let shuffled = (0..<strlen).shuffled()
return String(str.enumerated().map{
shuffled[$0.0] < effectiveCount || ($0.1) == " " ? ($0.1) : "_"
})}
//___l_ _l__gr____
//H____ p___g____d
func underscorize(_ str: String) -> String{
let effectiveStrlen = str.filter{$0 != " "}.count
let effectiveCount = Int(floor(Double(effectiveStrlen) * 0.25))
let shuffled = (0..<effectiveStrlen).shuffled()
return String((str.reduce(into: ([],0)) {
$0.0.append(shuffled[$0.1] <= effectiveCount || $1 == " " ? $1 : "_" )
$0.1 += ($1 == " ") ? 0 : 1}).0)
}
print(underscorize(str))
print(underscorize(str))
//__l__ pl__g_____
//___lo _l_______d

First you need to get the indices of your string and filter the ones that are letters. Then you can shuffle the result and pick the number of elements (%) minus the number of spaces in the original string, iterate through the result replacing the resulting ranges with the underscore.
You can extending RangeReplaceable protocol to be able to use it with substrings as well:
extension StringProtocol where Self: RangeReplaceableCollection{
mutating func randomReplace(characterSet: CharacterSet = .letters, percentage: Double, with element: Element = "_") {
precondition(0...1 ~= percentage)
let indices = self.indices.filter {
characterSet.contains(self[$0].unicodeScalars.first!)
}
let lettersCount = indices.count
let nonLettersCount = count - lettersCount
let n = lettersCount - nonLettersCount - Int(Double(lettersCount) * Double(1-percentage))
indices
.shuffled()
.prefix(n)
.forEach {
replaceSubrange($0...$0, with: Self([element]))
}
}
func randomReplacing(characterSet: CharacterSet = .letters, percentage: Double, with element: Element = "_") -> Self {
precondition(0...1 ~= percentage)
var result = self
result.randomReplace(characterSet: characterSet, percentage: percentage, with: element)
return result
}
}
// mutating test
var str = "Hello playground"
str.randomReplace(percentage: 0.75) // "___lo _l___r____\n"
print(str) // "___lo _l___r____\n"
// non mutating with another character
let str2 = "Hello playground"
str2.randomReplacing(percentage: 0.75, with: "•") // "••••o p••y•••u••"
print(str2) // "Hello playground\n"

Related

How to use Swift NSRegularExpression to get uppercased letter?

I have a string like this:
"te_st" and like to replace all underscores followed by a character with the uppercased version of this character.
From "te_st" --> Found (regex: "_.") --------replace with next char (+ uppercase ("s"->"S")--------> "teSt"
From "te_st" ---> to "teSt"
From "_he_l_lo" ---> to "HeLLo"
From "an_o_t_h_er_strin_g" ---> to "anOTHErStrinG"
... but I can not really get it working using Swift's NSRegularExpression like this small snipped does:
var result = "te_st" // result should be teSt
result = try! NSRegularExpression(pattern: "_*").stringByReplacingMatches(in: result, range: NSRange(0..<result.count), withTemplate: ("$1".uppercased()))
There's no regular syntax to convert a match to uppercase. The code you posted is attempting to convert the string $1 to uppercase which is of course just $1. It isn't attempting to convert the value represented by the $1 match at runtime.
Here's another approach using a regular expression to find the _ followed by a lowercase letter. Those are enumerated and replaced with the uppercase letter.
extension String {
func toCamelCase() -> String {
let expr = try! NSRegularExpression(pattern: "_([a-z])")
var res = self
for match in expr.matches(in: self, range: NSRange(0..<res.count)).reversed() {
let range = Range(match.range, in: self)!
let letterRange = Range(match.range(at: 1), in: self)!
res.replaceSubrange(range, with: self[letterRange].uppercased())
}
return res
}
}
print("te_st".toCamelCase())
print("_he_l_lo".toCamelCase())
print("an_o_t_h_er_strin_g".toCamelCase())
This outputs:
teSt
HeLLo
anOTHErStrinG
Here is one implementation using NSRegularExpression. I use group match to get the character after _ and capitalize it and replace the string.
func capitalizeLetterAfterUnderscore(string: String) -> String {
var capitalizedString = string
guard let regularExpression = try? NSRegularExpression(pattern: "_(.)") else {
return capitalizedString
}
let matches = regularExpression.matches(in: string,
options: .reportCompletion,
range: NSMakeRange(0, string.count))
for match in matches {
let groupRange = match.range(at: 1)
let index = groupRange.location
let characterIndex = string.index(string.startIndex,
offsetBy: index)
let range = characterIndex ... characterIndex
let capitalizedCharacter = String(capitalizedString[characterIndex]).capitalized
capitalizedString = capitalizedString.replacingCharacters(in: range,
with: capitalizedCharacter)
}
capitalizedString = capitalizedString.replacingOccurrences(of: "_", with: "")
return capitalizedString
}
capitalizeLetterAfterUnderscore(string: "an_o_t_h_er_strin_g") // anOTHErStrinG
And here is other one without using regular expression. I made extension for method which could also be reused.
extension String {
func indexes(of character: String) -> [Index] {
precondition(character.count == 1, "character should be single letter string")
return enumerated().reduce([]) { (partial, component) in
let currentIndex = index(startIndex,
offsetBy: component.offset)
return String(self[currentIndex]) == character
? partial + [currentIndex]
: partial
}
}
func capitalizeLetter(after indexes: [Index]) -> String {
var modifiedString = self
for currentIndex in indexes {
guard let letterIndex = index(currentIndex,
offsetBy: 1,
limitedBy: endIndex)
else { continue }
let range = letterIndex ... letterIndex
modifiedString = modifiedString.replacingCharacters(in: range,
with: self[range].capitalized)
}
return modifiedString
}
}
let string = "an_o_t_h_er_strin_g"
let newString = string.capitalizeLetter(after: string.indexes(of: "_"))
.replacingOccurrences(of: "_",with: "")
You can use string range(of:, options:, range:) method with .regularExpression options to match the occurrences of "_[a-z]" and replace the subranges iterating the ranges found at reversed order by the character at the index after the range lowerbound uppercased:
let string = "an_o_t_h_er_strin_g"
let regex = "_[a-z]"
var start = string.startIndex
var ranges:[Range<String.Index>] = []
while let range = string.range(of: regex, options: .regularExpression, range: start..<string.endIndex) {
start = range.upperBound
ranges.append(range)
}
var finalString = string
for range in ranges.reversed() {
finalString.replaceSubrange(range, with: String(string[string.index(after: range.lowerBound)]).uppercased())
}
print(finalString) // "anOTHErStrinG\n"
The problem is that it is converting the string "$1" to upper case (which is, unsurprisingly unchanged, just "$1") and using "$1" as the template. If you want to use regex, you will have to enumerate through matches yourself.
The alternative is to split the string by _ characters and uppercase the first character of every substring (except the first) and joining it back together using reduce:
let input = "te_st"
let output = input.components(separatedBy: "_").enumerated().reduce("") { $0 + ($1.0 == 0 ? $1.1 : $1.1.uppercasedFirst()) }
Or, if your goal isn't to write code as cryptic as most regex, we can make that a tad more legible:
let output = input
.components(separatedBy: "_")
.enumerated()
.reduce("") { result, current in
if current.offset == 0 {
return current.element // because you don’t want the first component capitalized
} else {
return result + current.element.uppercasedFirst()
}
}
Resulting in:
teSt
Note, that uses this extension for capitalizing the first character:
extension String {
func uppercasedFirst(with locale: Locale? = nil) -> String {
guard count > 0 else { return self }
return String(self[startIndex]).uppercased(with: locale) + self[index(after: startIndex)...]
}
}
If you want to do sort of dynamic conversion with NSRegularExpression, you can subclass NSRegularExpression and override replacementString(for:in:offset:template:):
class ToCamelRegularExpression: NSRegularExpression {
override func replacementString(for result: NSTextCheckingResult, in string: String, offset: Int, template templ: String) -> String {
if let range = Range(result.range(at: 1), in: string) {
return string[range].uppercased()
} else {
return super.replacementString(for: result, in: string, offset: 0, template: templ)
}
}
}
func toCamelCase(_ input: String) -> String { //Make this a String extension if you prefer...
let regex = try! ToCamelRegularExpression(pattern: "_(.)")
return regex.stringByReplacingMatches(in: input, options: [], range: NSRange(0..<input.utf16.count), withTemplate: "$1")
}
print(toCamelCase("te_st")) //-> teSt
print(toCamelCase("_he_l_lo")) //-> HeLLo
print(toCamelCase("an_o_t_h_er_strin_g")) //-> anOTHErStrinG

Split string to arrays with maximum variables in each array

I have a string of numbers (each number is separated by ,) that looks like this:
"12,3,5,75,584,364,57,88,94,4,79,333,7465,867,56,6,748,546,573,466"
I want to split the string to an array of strings, that each element is a string that has maximum 10 number in it.
For the example I've added I want to achieve something like this:
stringsArray:
Element 0: "12,3,5,75,584,364,57,88,94,4"
Element 1: "79,333,7465,867,56,6,748,546,573,466"
And so on...
I've been thinking a lot about a way to do this with Swift, but couldn't find anything...
Does anybody has an idea?
Thank you!
Step 1 - get fully separated array:
let numbers = "12,3,5".components(separatedBy: ",")
Step 2 - chunk your result to parts with ext:
extension Array {
func chunked(by chunkSize: Int) -> [[Element]] {
return stride(from: 0, to: self.count, by: chunkSize).map {
Array(self[$0..<Swift.min($0 + chunkSize, self.count)])
}
}
}
let chunkedNumbers = numbers.chunked(by: 10)
Step 3:
let stringsArray = chunkedNumbers.map { $0.joined(separator: ",") }
Result: ["12,3,5,75,584,364,57,88,94,4", "79,333,7465,867,56,6,748,546,573,466"]
Link to gist playground.
I would look at the position of 10th comma in your original string, get the prefix up to this position, remove this prefix and repeat until remaining string is empty.
This is a bit brute force, but works.
I first add extension to String for convenience.
extension String {
func startIndexesOf(_ string: String) -> [Int] {
var result: [Int] = []
var start = startIndex
while let range = range(of: string, options: .literal, range: start..<endIndex) {
result.append(range.lowerBound.encodedOffset)
start = range.upperBound
}
return result
}
subscript (r: Range<Int>) -> String {
let start = index(self.startIndex, offsetBy: r.lowerBound)
let end = self.index(self.startIndex, offsetBy: r.upperBound)
return String(self[Range(start ..< end)])
}
}
let test = "12,3,5,75,584,364,57,88,94,4,79,333,7465,867,56,6,748,546,573,466,999"
var remaining = test
var arrayOf10 : [String] = []
repeat {
let indexes = remaining.startIndexesOf(",")
if indexes.count < 10 {
arrayOf10.append(remaining) // Just add what remains
break
}
let position = indexes[9]
let endBeginning = remaining.index(test.startIndex, offsetBy: position) // Beginning of what remain to parse
let beginningSubstring = remaining[remaining.startIndex ..< endBeginning]
let beginningText = String(beginningSubstring)
arrayOf10.append(beginningText)
let startNext = remaining.index(test.startIndex, offsetBy: position+1) // What will remain to parse after taking out the beginning
let remainingSubString = remaining[startNext ..< remaining.endIndex]
remaining = String(remainingSubString)
} while remaining.count > 0
for (c, s) in arrayOf10.enumerated() { print("Element", c, ": ", s)}
This will print as desired
Element 0 : 12,3,5,75,584,364,57,88,94,4
Element 1 : 79,333,7465,867,56,6,748,546,573,466
Element 2 : 999

How to get `printf` to work as expected using Swift String? [duplicate]

What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}

Swift: Format String width

What I'm wanting to do is very simple in C/C++, Java, and so many other languages. All I want to do is be able to specify the width of a string, similar to this:
printf("%-15s", var);
This would create of a field width of 15 characters. I've done a lot of googling. I've tried using COpaquepointeras well as String(format:in various ways with no luck. Any suggestions would be greatly appreciated. I could have missed something when googling.
You can use withCString to quickly convert the string to an array of bytes (technically an UnsafePointer<Int8>):
let str = "Hello world"
let formatted = str.withCString { String(format: "%-15s", $0) }
print("'\(formatted)'")
You are better to do it yourself
let str0 = "alpha"
let length = 20
// right justify
var str20r = String(count: (length - str0.characters.count), repeatedValue: Character(" "))
str20r.appendContentsOf(str0)
// " alpha"
// left justify
var str20l = str0
str20l.appendContentsOf(String(count: (length - str0.characters.count), repeatedValue: Character(" ")))
// "alpha "
if you need something 'more general'
func formatString(str: String, fixLenght: Int, spacer: Character = Character(" "), justifyToTheRigth: Bool = false)->String {
let c = str.characters.count
let start = str.characters.startIndex
let end = str.characters.endIndex
var str = str
if c > fixLenght {
switch justifyToTheRigth {
case true:
let range = start.advancedBy(c - fixLenght)..<end
return String(str.characters[range])
case false:
let range = start..<end.advancedBy(fixLenght - c)
return String(str.characters[range])
}
} else {
var extraSpace = String(count: fixLenght - c, repeatedValue: spacer)
if justifyToTheRigth {
extraSpace.appendContentsOf(str)
return extraSpace
} else {
str.appendContentsOf(extraSpace)
return str
}
}
}
let str = "ABCDEFGH"
let s0 = formatString(str, fixLenght: 3)
let s1 = formatString(str, fixLenght: 3, justifyToTheRigth: true)
let s2 = formatString(str, fixLenght: 10, spacer: Character("-"))
let s3 = formatString(str, fixLenght: 10, spacer: Character("-"), justifyToTheRigth: true)
print(s0)
print(s1)
print(s2)
print(s3)
which prints
ABC
FGH
ABCDEFGH--
--ABCDEFGH
The problem is that Swift strings have variable size elements, so it's ambiguous what "15 characters" is. This is a source of frustration for simple strings — but makes the language more precise when dealing with emoji, regional identifiers, ligatures, etc.
You can convert the Swift string to a C-string and use normal formatters (see Santosh's answer). The "Swift" way to handle strings is to begin at the starting index of the collection of Characters and advance N times. For example:
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let index = alphabet.characters.startIndex.advancedBy(14) // String.CharacterView.Index
let allChars = alphabet.characters.prefixThrough(index) // String.CharacterView
print(String(allChars)) // "ABCDEFGHIJKLMNO\n"
If you want to force padding, you could use an approach like this:
extension String {
func formatted(characterCount characterCount:Int) -> String {
if characterCount < characters.count {
return String(characters.prefixThrough(characters.startIndex.advancedBy(characterCount - 1)))
} else {
return self + String(count: characterCount - characters.count, repeatedValue: " " as Character)
}
}
}
let abc = "ABC"
let alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
print("!\(abc.formatted(characterCount: 15))!")
// "!ABC !\n"
print("!\(alphabet.formatted(characterCount: 15))!")
// "!ABCDEFGHIJKLMNOP!\n"
Did you try this?
let string1 = "string1"
let string2 = "string2"
let formattedString = String(format: "%-15s - %s",
COpaquePointer(string1.cStringUsingEncoding(NSUTF8StringEncoding)!),
COpaquePointer(string2.cStringUsingEncoding(NSUTF8StringEncoding)!)
)
print(formattedString)
//string1 - string2
We've got a ton of interesting answers now. Thank you everyone. I wrote the following:
func formatLeftJustifiedWidthSpecifier(stringToChange: String, width: Int) -> String {
var newString: String = stringToChange
newString = newString.stringByPaddingToLength(width, withString: " ", startingAtIndex: 0)
return newString
}
From one hand %# is used to format String objects:
import Foundation
var str = "Hello"
print(String(format: "%#", str))
But it does not support the width modifier:
print(String(format: "%-15#", str))
Will still print unpadded text:
"Hello\n"
However there is a modifier %s that seems to work with CStrings:
var cstr = (str as NSString).utf8String //iOS10+ or .UTF8String otherwise
print(String(format: "%-15s", cstr!))
Output:
"Hello \n"
One nice thing is that you can use the same format specification with NSLog:
NSLog("%-15s", cstr!)
To augment the answer above by "Code Different" (thank you!) on Jun 29, 2016, and allow to write something like "hello".center(42); "world".alignLeft(42):
extension String {
// note: symbol names match to nim std/strutils lib
func align (_ boxsz: UInt) -> String {
self.withCString { String(format: "%\(boxsz)s", $0) }
}
func alignLeft (_ boxsz: UInt) -> String {
self.withCString { String(format: "%-\(boxsz)s", $0) }
}
func center (_ boxsz: UInt) -> String {
let n = self.count
guard boxsz > n else { return self }
let padding = boxsz - UInt(n)
let R = padding / 2
guard R > 0 else { return " " + self }
let L = (padding%2 == 0) ? R : (R+1)
return " ".withCString { String(format: "%\(L)s\(self)%\(R)s", $0,$0) }
}
}

How do you use String.substringWithRange? (or, how do Ranges work in Swift?)

I have not yet been able to figure out how to get a substring of a String in Swift:
var str = “Hello, playground”
func test(str: String) -> String {
return str.substringWithRange( /* What goes here? */ )
}
test (str)
I'm not able to create a Range in Swift. Autocomplete in the Playground isn’t super helpful - this is what it suggests:
return str.substringWithRange(aRange: Range<String.Index>)
I haven't found anything in the Swift Standard Reference Library that helps. Here was another wild guess:
return str.substringWithRange(Range(0, 1))
And this:
let r:Range<String.Index> = Range<String.Index>(start: 0, end: 2)
return str.substringWithRange(r)
I've seen other answers (Finding index of character in Swift String) that seem to suggest that since String is a bridge type for NSString, the "old" methods should work, but it's not clear how - e.g., this doesn't work either (doesn't appear to be valid syntax):
let x = str.substringWithRange(NSMakeRange(0, 3))
Thoughts?
You can use the substringWithRange method. It takes a start and end String.Index.
var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex, end: str.endIndex)) //"Hello, playground"
To change the start and end index, use advancedBy(n).
var str = "Hello, playground"
str.substringWithRange(Range<String.Index>(start: str.startIndex.advancedBy(2), end: str.endIndex.advancedBy(-1))) //"llo, playgroun"
You can also still use the NSString method with NSRange, but you have to make sure you are using an NSString like this:
let myNSString = str as NSString
myNSString.substringWithRange(NSRange(location: 0, length: 3))
Note: as JanX2 mentioned, this second method is not safe with unicode strings.
Swift 2
Simple
let str = "My String"
let subStr = str[str.startIndex.advancedBy(3)...str.startIndex.advancedBy(7)]
//"Strin"
Swift 3
let startIndex = str.index(str.startIndex, offsetBy: 3)
let endIndex = str.index(str.startIndex, offsetBy: 7)
str[startIndex...endIndex] // "Strin"
str.substring(to: startIndex) // "My "
str.substring(from: startIndex) // "String"
Swift 4
substring(to:) and substring(from:) are deprecated in Swift 4.
String(str[..<startIndex]) // "My "
String(str[startIndex...]) // "String"
String(str[startIndex...endIndex]) // "Strin"
At the time I'm writing, no extension is perfectly Swift 4.2 compatible, so here is one that covers all the needs I could think of:
extension String {
func substring(from: Int?, to: Int?) -> String {
if let start = from {
guard start < self.count else {
return ""
}
}
if let end = to {
guard end >= 0 else {
return ""
}
}
if let start = from, let end = to {
guard end - start >= 0 else {
return ""
}
}
let startIndex: String.Index
if let start = from, start >= 0 {
startIndex = self.index(self.startIndex, offsetBy: start)
} else {
startIndex = self.startIndex
}
let endIndex: String.Index
if let end = to, end >= 0, end < self.count {
endIndex = self.index(self.startIndex, offsetBy: end + 1)
} else {
endIndex = self.endIndex
}
return String(self[startIndex ..< endIndex])
}
func substring(from: Int) -> String {
return self.substring(from: from, to: nil)
}
func substring(to: Int) -> String {
return self.substring(from: nil, to: to)
}
func substring(from: Int?, length: Int) -> String {
guard length > 0 else {
return ""
}
let end: Int
if let start = from, start > 0 {
end = start + length - 1
} else {
end = length - 1
}
return self.substring(from: from, to: end)
}
func substring(length: Int, to: Int?) -> String {
guard let end = to, end > 0, length > 0 else {
return ""
}
let start: Int
if let end = to, end - length > 0 {
start = end - length + 1
} else {
start = 0
}
return self.substring(from: start, to: to)
}
}
And then, you can use:
let string = "Hello,World!"
string.substring(from: 1, to: 7)gets you: ello,Wo
string.substring(to: 7)gets you: Hello,Wo
string.substring(from: 3)gets you: lo,World!
string.substring(from: 1, length: 4)gets you: ello
string.substring(length: 4, to: 7)gets you: o,Wo
Updated substring(from: Int?, length: Int) to support starting from zero.
NOTE: #airspeedswift makes some very insightful points on the trade-offs of this approach, particularly the hidden performance impacts. Strings are not simple beasts, and getting to a particular index may take O(n) time, which means a loop that uses a subscript can be O(n^2). You have been warned.
You just need to add a new subscript function that takes a range and uses advancedBy() to walk to where you want:
import Foundation
extension String {
subscript (r: Range<Int>) -> String {
get {
let startIndex = self.startIndex.advancedBy(r.startIndex)
let endIndex = startIndex.advancedBy(r.endIndex - r.startIndex)
return self[Range(start: startIndex, end: endIndex)]
}
}
}
var s = "Hello, playground"
println(s[0...5]) // ==> "Hello,"
println(s[0..<5]) // ==> "Hello"
(This should definitely be part of the language. Please dupe rdar://17158813)
For fun, you can also add a + operator onto the indexes:
func +<T: ForwardIndex>(var index: T, var count: Int) -> T {
for (; count > 0; --count) {
index = index.succ()
}
return index
}
s.substringWithRange(s.startIndex+2 .. s.startIndex+5)
(I don't know yet if this one should be part of the language or not.)
SWIFT 2.0
simple:
let myString = "full text container"
let substring = myString[myString.startIndex..<myString.startIndex.advancedBy(3)] // prints: ful
SWIFT 3.0
let substring = myString[myString.startIndex..<myString.index(myString.startIndex, offsetBy: 3)] // prints: ful
SWIFT 4.0
Substring operations return an instance of the Substring type, instead of String.
let substring = myString[myString.startIndex..<myString.index(myString.startIndex, offsetBy: 3)] // prints: ful
// Convert the result to a String for long-term storage.
let newString = String(substring)
It is much more simple than any of the answers here, once you find the right syntax.
I want to take away the [ and ]
let myString = "[ABCDEFGHI]"
let startIndex = advance(myString.startIndex, 1) //advance as much as you like
let endIndex = advance(myString.endIndex, -1)
let range = startIndex..<endIndex
let myNewString = myString.substringWithRange( range )
result will be "ABCDEFGHI"
the startIndex and endIndex could also be used in
let mySubString = myString.substringFromIndex(startIndex)
and so on!
PS: As indicated in the remarks, there are some syntax changes in swift 2 which comes with xcode 7 and iOS9!
Please look at this page
For example to find the first name (up to the first space) in my full name:
let name = "Joris Kluivers"
let start = name.startIndex
let end = find(name, " ")
if end {
let firstName = name[start..end!]
} else {
// no space found
}
start and end are of type String.Index here and are used to create a Range<String.Index> and used in the subscript accessor (if a space is found at all in the original string).
It's hard to create a String.Index directly from an integer position as used in the opening post. This is because in my name each character would be of equal size in bytes. But characters using special accents in other languages could have used several more bytes (depending on the encoding used). So what byte should the integer refer to?
It's possible to create a new String.Index from an existing one using the methods succ and pred which will make sure the correct number of bytes are skipped to get to the next code point in the encoding. However in this case it's easier to search for the index of the first space in the string to find the end index.
Since String is a bridge type for NSString, the "old" methods should work, but it's not clear how - e.g., this doesn't work either (doesn't appear to be valid syntax):
let x = str.substringWithRange(NSMakeRange(0, 3))
To me, that is the really interesting part of your question. String is bridged to NSString, so most NSString methods do work directly on a String. You can use them freely and without thinking. So, for example, this works just as you expect:
// delete all spaces from Swift String stateName
stateName = stateName.stringByReplacingOccurrencesOfString(" ", withString:"")
But, as so often happens, "I got my mojo workin' but it just don't work on you." You just happened to pick one of the rare cases where a parallel identically named Swift method exists, and in a case like that, the Swift method overshadows the Objective-C method. Thus, when you say str.substringWithRange, Swift thinks you mean the Swift method rather than the NSString method — and then you are hosed, because the Swift method expects a Range<String.Index>, and you don't know how to make one of those.
The easy way out is to stop Swift from overshadowing like this, by casting explicitly:
let x = (str as NSString).substringWithRange(NSMakeRange(0, 3))
Note that no significant extra work is involved here. "Cast" does not mean "convert"; the String is effectively an NSString. We are just telling Swift how to look at this variable for purposes of this one line of code.
The really weird part of this whole thing is that the Swift method, which causes all this trouble, is undocumented. I have no idea where it is defined; it is not in the NSString header and it's not in the Swift header either.
The short answer is that this is really hard in Swift right now. My hunch is that there is still a bunch of work for Apple to do on convenience methods for things like this.
String.substringWithRange() is expecting a Range<String.Index> parameter, and as far as I can tell there isn't a generator method for the String.Index type. You can get String.Index values back from aString.startIndex and aString.endIndex and call .succ() or .pred() on them, but that's madness.
How about an extension on the String class that takes good old Ints?
extension String {
subscript (r: Range<Int>) -> String {
get {
let subStart = advance(self.startIndex, r.startIndex, self.endIndex)
let subEnd = advance(subStart, r.endIndex - r.startIndex, self.endIndex)
return self.substringWithRange(Range(start: subStart, end: subEnd))
}
}
func substring(from: Int) -> String {
let end = countElements(self)
return self[from..<end]
}
func substring(from: Int, length: Int) -> String {
let end = from + length
return self[from..<end]
}
}
let mobyDick = "Call me Ishmael."
println(mobyDick[8...14]) // Ishmael
let dogString = "This 🐶's name is Patch."
println(dogString[5..<6]) // 🐶
println(dogString[5...5]) // 🐶
println(dogString.substring(5)) // 🐶's name is Patch.
println(dogString.substring(5, length: 1)) // 🐶
Update: Swift beta 4 resolves the issues below!
As it stands [in beta 3 and earlier], even Swift-native strings have some issues with handling Unicode characters. The dog icon above worked, but the following doesn't:
let harderString = "1:1️⃣"
for character in harderString {
println(character)
}
Output:
1
:
1
️
⃣
In new Xcode 7.0 use
//: Playground - noun: a place where people can play
import UIKit
var name = "How do you use String.substringWithRange?"
let range = name.startIndex.advancedBy(0)..<name.startIndex.advancedBy(10)
name.substringWithRange(range)
//OUT:
You can use this extensions to improve substringWithRange
Swift 2.3
extension String
{
func substringWithRange(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(end))
return self.substringWithRange(range)
}
func substringWithRange(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let range = Range(start: self.startIndex.advancedBy(start), end: self.startIndex.advancedBy(start + location))
return self.substringWithRange(range)
}
}
Swift 3
extension String
{
func substring(start: Int, end: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if end < 0 || end > self.characters.count
{
print("end index \(end) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: end)
let range = startIndex..<endIndex
return self.substring(with: range)
}
func substring(start: Int, location: Int) -> String
{
if (start < 0 || start > self.characters.count)
{
print("start index \(start) out of bounds")
return ""
}
else if location < 0 || start + location > self.characters.count
{
print("end index \(start + location) out of bounds")
return ""
}
let startIndex = self.characters.index(self.startIndex, offsetBy: start)
let endIndex = self.characters.index(self.startIndex, offsetBy: start + location)
let range = startIndex..<endIndex
return self.substring(with: range)
}
}
Usage:
let str = "Hello, playground"
let substring1 = str.substringWithRange(0, end: 5) //Hello
let substring2 = str.substringWithRange(7, location: 10) //playground
Sample Code for how to get substring in Swift 2.0
(i) Substring from starting index
Input:-
var str = "Swift is very powerful language!"
print(str)
str = str.substringToIndex(str.startIndex.advancedBy(5))
print(str)
Output:-
Swift is very powerful language!
Swift
(ii) Substring from particular index
Input:-
var str = "Swift is very powerful language!"
print(str)
str = str.substringFromIndex(str.startIndex.advancedBy(6)).substringToIndex(str.startIndex.advancedBy(2))
print(str)
Output:-
Swift is very powerful language!
is
I hope it will help you!
Easy solution with little code.
Make an extension that includes basic subStringing that nearly all other languages have:
extension String {
func subString(start: Int, end: Int) -> String {
let startIndex = self.index(self.startIndex, offsetBy: start)
let endIndex = self.index(startIndex, offsetBy: end)
let finalString = self.substring(from: startIndex)
return finalString.substring(to: endIndex)
}
}
Simply call this with
someString.subString(start: 0, end: 6)
This works in my playground :)
String(seq: Array(str)[2...4])
Updated for Xcode 7. Adds String extension:
Use:
var chuck: String = "Hello Chuck Norris"
chuck[6...11] // => Chuck
Implementation:
extension String {
/**
Subscript to allow for quick String substrings ["Hello"][0...1] = "He"
*/
subscript (r: Range<Int>) -> String {
get {
let start = self.startIndex.advancedBy(r.startIndex)
let end = self.startIndex.advancedBy(r.endIndex - 1)
return self.substringWithRange(start..<end)
}
}
}
try this in playground
var str:String = "Hello, playground"
let range = Range(start:advance(str.startIndex,1), end: advance(str.startIndex,8))
it will give you "ello, p"
However where this gets really interesting is that if you make the last index bigger than the string in playground it will show any strings that you defined after str :o
Range() appears to be a generic function so that it needs to know the type it is dealing with.
You also have to give it the actual string your interested in playgrounds as it seems to hold all stings in a sequence one after another with their variable name afterwards.
So
var str:String = "Hello, playground"
var str2:String = "I'm the next string"
let range = Range(start:advance(str.startIndex,1), end: advance(str.startIndex,49))
gives "ello, playground�str���I'm the next string�str2�"
works even if str2 is defined with a let
:)
Rob Napier had already given a awesome answer using subscript. But i felt one drawback in that as there is no check for out of bound conditions. This can tend to crash. So i modified the extension and here it is
extension String {
subscript (r: Range<Int>) -> String? { //Optional String as return value
get {
let stringCount = self.characters.count as Int
//Check for out of boundary condition
if (stringCount < r.endIndex) || (stringCount < r.startIndex){
return nil
}
let startIndex = self.startIndex.advancedBy(r.startIndex)
let endIndex = self.startIndex.advancedBy(r.endIndex - r.startIndex)
return self[Range(start: startIndex, end: endIndex)]
}
}
}
Output below
var str2 = "Hello, World"
var str3 = str2[0...5]
//Hello,
var str4 = str2[0..<5]
//Hello
var str5 = str2[0..<15]
//nil
So i suggest always to check for the if let
if let string = str[0...5]
{
//Manipulate your string safely
}
In Swift3
For ex: a variable "Duke James Thomas", we need to get "James".
let name = "Duke James Thomas"
let range: Range<String.Index> = name.range(of:"James")!
let lastrange: Range<String.Index> = img.range(of:"Thomas")!
var middlename = name[range.lowerBound..<lstrange.lowerBound]
print (middlename)
Taking a page from Rob Napier, I developed these Common String Extensions, two of which are:
subscript (r: Range<Int>) -> String
{
get {
let startIndex = advance(self.startIndex, r.startIndex)
let endIndex = advance(self.startIndex, r.endIndex - 1)
return self[Range(start: startIndex, end: endIndex)]
}
}
func subString(startIndex: Int, length: Int) -> String
{
var start = advance(self.startIndex, startIndex)
var end = advance(self.startIndex, startIndex + length)
return self.substringWithRange(Range<String.Index>(start: start, end: end))
}
Usage:
"Awesome"[3...7] //"some"
"Awesome".subString(3, length: 4) //"some"
This is how you get a range from a string:
var str = "Hello, playground"
let startIndex = advance(str.startIndex, 1)
let endIndex = advance(startIndex, 8)
let range = startIndex..<endIndex
let substr = str[range] //"ello, pl"
The key point is that you are passing a range of values of type String.Index (this is what advance returns) instead of integers.
The reason why this is necessary, is that strings in Swift don't have random access (because of variable length of Unicode characters basically). You also can't do str[1]. String.Index is designed to work with their internal structure.
You can create an extension with a subscript though, that does this for you, so you can just pass a range of integers (see e.g. Rob Napier's answer).
I tried to come up with something Pythonic.
All the subscripts here are great, but the times I really need something simple is usually when I want to count from back, e.g. string.endIndex.advancedBy(-1)
It supports nil values, for both start and end index, where nil would mean index at 0 for start, string.characters.count for end.
extension String {
var subString: (Int?) -> (Int?) -> String {
return { (start) in
{ (end) in
let startIndex = start ?? 0 < 0 ? self.endIndex.advancedBy(start!) : self.startIndex.advancedBy(start ?? 0)
let endIndex = end ?? self.characters.count < 0 ? self.endIndex.advancedBy(end!) : self.startIndex.advancedBy(end ?? self.characters.count)
return startIndex > endIndex ? "" : self.substringWithRange(startIndex ..< endIndex)
}
}
}
}
let dog = "Dog‼🐶"
print(dog.subString(nil)(-1)) // Dog!!
EDIT
A more elegant solution:
public extension String {
struct Substring {
var start: Int?
var string: String
public subscript(end: Int?) -> String {
let startIndex = start ?? 0 < 0 ? string.endIndex.advancedBy(start!) : string.startIndex.advancedBy(start ?? 0)
let endIndex = end ?? string.characters.count < 0 ? string.endIndex.advancedBy(end!) : string.startIndex.advancedBy(end ?? string.characters.count)
return startIndex > endIndex ? "" : string.substringWithRange(startIndex ..< endIndex)
}
}
public subscript(start: Int?) -> Substring {
return Substring(start: start, string: self)
}
}
let dog = "Dog‼🐶"
print(dog[nil][-1]) // Dog!!
First create the range, then the substring. You can use fromIndex..<toIndex syntax like so:
let range = fullString.startIndex..<fullString.startIndex.advancedBy(15) // 15 first characters of the string
let substring = fullString.substringWithRange(range)
here is a example to get video-Id only .i.e (6oL687G0Iso) from the whole URL in swift
let str = "https://www.youtube.com/watch?v=6oL687G0Iso&list=PLKmzL8Ib1gsT-5LN3V2h2H14wyBZTyvVL&index=2"
var arrSaprate = str.componentsSeparatedByString("v=")
let start = arrSaprate[1]
let rangeOfID = Range(start: start.startIndex,end:start.startIndex.advancedBy(11))
let substring = start[rangeOfID]
print(substring)
let startIndex = text.startIndex
var range = startIndex.advancedBy(1) ..< text.endIndex.advancedBy(-4)
let substring = text.substringWithRange(range)
Full sample you can see here
http://www.learnswiftonline.com/reference-guides/string-reference-guide-for-swift/
shows that this works well:
var str = "abcd"
str = str.substringToIndex(1)
Well, I had the same issue and solved with the "bridgeToObjectiveC()" function:
var helloworld = "Hello World!"
var world = helloworld.bridgeToObjectiveC().substringWithRange(NSMakeRange(6,6))
println("\(world)") // should print World!
Please note that in the example, substringWithRange in conjunction with NSMakeRange take the part of the string starting at index 6 (character "W") and finishing at index 6 + 6 positions ahead (character "!")
Cheers.
You can use any of the substring methods in a Swift String extension I wrote https://bit.ly/JString.
var string = "hello"
var sub = string.substringFrom(3) // or string[3...5]
println(sub)// "lo"
If you have an NSRange, bridging to NSString works seamlessly. For example, I was doing some work with UITextFieldDelegate and I quickly wanted to compute the new string value when it asked if it should replace the range.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text as NSString).stringByReplacingCharactersInRange(range, withString: string)
println("Got new string: ", newString)
}
Simple extension for String:
extension String {
func substringToIndex(index: Int) -> String {
return self[startIndex...startIndex.advancedBy(min(index, characters.count - 1))]
}
}
If you don't care about performance... this is probably the most concise solution in Swift 4
extension String {
subscript(range: CountableClosedRange<Int>) -> String {
return enumerated().filter{$0.offset >= range.first! && $0.offset < range.last!}
.reduce(""){$0 + String($1.element)}
}
}
It enables you to do something like this:
let myStr = "abcd"
myStr[0..<2] // produces "ab"