Rotate Swift Array - swift

I am trying to solve this question I found on a coding challenge website using Swift 3.
I'm sure most of you have seen it before, but in case you haven't here it is...
The idea is you take a string and rotate it x number of times. So using their example "12345" rotated 2x would be "34512"
I wrote this, but it when I print it out in Playground it just prints out the exact same string I entered.
func rotateSring(originalString: String, numberOfRotations: Int) -> String {
var tempArray: [String] = []
tempArray.append(originalString)
let count = numberOfRotations
for _ in 1...count {
for letter in tempArray {
tempArray.remove(at: 0)
tempArray.append(letter)
}
}
let newString = tempArray.joined(separator: "")
return newString
}
I also tried a variation
func rotateSring(originalString: String, numberOfRotations: Int) -> String {
var tempArray: [String] = []
tempArray.append(originalString)
let count = numberOfRotations
for _ in 1...count {
let test =tempArray.remove(at: 0)
tempArray.append(test)
}
let newString = tempArray.joined(separator: "")
return newString
}
Neither produce the desired result when I say
let testRun = rotateSring(originalString: "12345", numberOfRotations: 2)
I would like the "34512" but instead I get "12345"
If I had to guess what I am doing wrong, I think that I am just rotating the entire array from start to finish so it does move but it moves full circle.
If somebody could explain what I am doing wrong, and how I can fix it that would be great. Thank you

I have gone through your solution and found few mistakes. The below implementation will work.
func rotateSring(originalString: String, numberOfRotations: Int) -> String {
var tempArray: [Character] = Array(originalString.characters)
let count = numberOfRotations
for _ in 1...count {
let letter = tempArray.removeFirst()
tempArray.append(letter)
}
let newString = String(tempArray)
return newString
}
let testRun = rotateSring(originalString: "12345", numberOfRotations: 2)
Now let me explain the changes:
var tempArray: [String] = []
tempArray.append(originalString)
// to
var tempArray: [Character] = Array(originalString.characters)
In Swift, String doesn't conform to Sequence type protocol and so you need to use Character array and so when you were trying to loop over letters, you were actually looping over the whole string i.e. 12345.
// tempArray = ["12345"] not ["1", "2", "3", "4", "5"]
for letter in tempArray {
tempArray.remove(at: 0)
tempArray.append(letter)
}

func rotateSring(originalString: String, numberOfRotations: UInt) -> String {
if numberOfRotations == 0 {
return originalString
}
return rotateSring(originalString: originalString[originalString.index(after: originalString.startIndex)..<originalString.endIndex] + String(originalString[originalString.startIndex]),
numberOfRotations: numberOfRotations - 1)
}

The native String's padding function can do that for you quite efficiently :
let string = "12345"
let rotation = 2
let rotated = "".padding(toLength: string.characters.count, withPad: string, startingAt: rotation % string.characters.count)
if you also need to support negative rotation values, you simply need to calculate the appropriate positive offset:
let string = "12345"
let rotation = -3
let offset = ( rotation % string.characters.count + string.characters.count ) % string.characters.count
let rotated = "".padding(toLength: string.characters.count, withPad: string, startingAt: offset)

What you are doing wrongly in both tries is that you used a [String] with only one element in it - originalString. So when you remove the element at index 0, the array becomes empty.
Here is a solution of mine:
func rotateSring(originalString: String, numberOfRotations: Int) -> String {
var str = originalString
for _ in 0..<numberOfRotations {
let firstChar = str.characters.first! // temporarily store the first char
var c = str.characters.dropFirst() // remove the first char from the string
c.append(firstChar) // add the first char back to the end
str.characters = c
}
return str
}

Rotation by using substring(to:) and substring(from:)
The accepted answer have already covered fixing your own solution; I'll pitch in with another alternative, making use of the substring(to:) and substring(from:) methods of String to rotate a given string a supplied number of characters. The supplied String will be left-rotated (<- shift) for positive rotation numbers, and right-rotated for (-> shift) for negative numbers.
// "left-rotate" supplied string using substring concenation
// (negative supplied rotations will be applied as "right-rotations")
func rotateString(originalString: String, numberOfRotations: Int) -> String {
// rotation is a non-changing operation upon empty or single-character strings
guard case let charCount = originalString.characters.count,
charCount > 1 else { return originalString }
// remove redundant full cycle rotation, and change rotation
// direction (left -> right) in case the supplied rotations are negative.
let numberOfRotations = numberOfRotations % charCount
+ (numberOfRotations < 0 ? 1 : 0) * charCount
// use substring methods to construct the "rotated" String
if numberOfRotations != 0 {
let splitIndex = originalString
.index(originalString.startIndex, offsetBy: numberOfRotations)
return originalString.substring(from: splitIndex) +
originalString.substring(to: splitIndex)
}
return originalString
}
Example usage:
let str = "1πŸ‡―πŸ‡΅345"
// left rotations
print(rotateString(originalString: str, numberOfRotations: 1)) // πŸ‡―πŸ‡΅3451
print(rotateString(originalString: str, numberOfRotations: 2)) // 3451πŸ‡―πŸ‡΅
print(rotateString(originalString: str, numberOfRotations: 6)) // πŸ‡―πŸ‡΅3451
// right rotations
print(rotateString(originalString: str, numberOfRotations: -2)) // 451πŸ‡―πŸ‡΅3
print(rotateString(originalString: str, numberOfRotations: -6)) // 51πŸ‡―πŸ‡΅34
// no rotations (/ only full cycles)
print(rotateString(originalString: str, numberOfRotations: 5)) // 1πŸ‡―πŸ‡΅345
print(rotateString(originalString: str, numberOfRotations: -5)) // 1πŸ‡―πŸ‡΅345
print(rotateString(originalString: str, numberOfRotations: 0)) // 1πŸ‡―πŸ‡΅345
Or, as a String extension:
extension String {
func rotated(by numberOfRotations: Int) -> String {
guard case let charCount = characters.count,
charCount > 1 else { return self }
let numberOfRotations = numberOfRotations % charCount
+ (numberOfRotations < 0 ? 1 : 0) * charCount
if numberOfRotations != 0 {
let splitIndex = index(startIndex, offsetBy: numberOfRotations)
return substring(from: splitIndex) + substring(to: splitIndex)
}
return self
}
}
/* example usage */
let str = "1πŸ‡―πŸ‡΅345"
// left rotations
print(str.rotated(by: 1)) // πŸ‡―πŸ‡΅3451
print(str.rotated(by: 2)) // 3451πŸ‡―πŸ‡΅
print(str.rotated(by: 6)) // πŸ‡―πŸ‡΅3451
// right rotations
print(str.rotated(by: -2)) // 451πŸ‡―πŸ‡΅3
print(str.rotated(by: -6)) // 51πŸ‡―πŸ‡΅34
// no rotations (/ only full cycles)
print(str.rotated(by: 5)) // 1πŸ‡―πŸ‡΅345
print(str.rotated(by: -5)) // 1πŸ‡―πŸ‡΅345
print(str.rotated(by: 0)) // 1πŸ‡―πŸ‡΅345

code
extension String {
mutating func rotate(by count_: Int) {
guard count_ != 0 else { return }
if count_ < 0 {
let count = -count_ % self.count
let tailRange = index(endIndex, offsetBy: -count)..<endIndex
let tail = String(self[tailRange])
removeSubrange(tailRange)
self = tail + self
} else {
let count = count_ % self.count
let headRange = startIndex..<index(startIndex, offsetBy: count)
let head = String(self[headRange])
removeSubrange(headRange)
self = self + head
}
}
mutating func rotated(by count: Int) -> String {
rotate(by: count)
return self
}
}
Although it's not the most performant way, you can solve this with a recursive function as well:
mutating func rotate(by count_: Int) {
guard count_ >= 0 else { return rotate(by: count - (-count_ % count)) }
let count = count_ % self.count
let headRange = startIndex..<index(startIndex, offsetBy: count)
let head = String(self[headRange])
removeSubrange(headRange)
self = self + head
}
usage
var english = "ABCDEFGHIJ"
var hangeul = "ㅁλͺ¨λͺΈλ§ˆλ§˜λ«„λ«”"
english.rotate(by: 3)
print(english) //DEFGHIJABC
print(hangeul.rotated(by: 1)) //λͺ¨λͺΈλ§ˆλ§˜λ«„뫔ㅁ

Related

substring(with:)' is deprecated: Please use String slicing subscript [duplicate]

I have the following simple code written in Swift 3:
let str = "Hello, playground"
let index = str.index(of: ",")!
let newStr = str.substring(to: index)
From Xcode 9 beta 5, I get the following warning:
'substring(to:)' is deprecated: Please use String slicing subscript with a 'partial range from' operator.
How can this slicing subscript with partial range from be used in Swift 4?
You should leave one side empty, hence the name "partial range".
let newStr = str[..<index]
The same stands for partial range from operators, just leave the other side empty:
let newStr = str[index...]
Keep in mind that these range operators return a Substring. If you want to convert it to a string, use String's initialization function:
let newStr = String(str[..<index])
You can read more about the new substrings here.
Convert Substring (Swift 3) to String Slicing (Swift 4)
Examples In Swift 3, 4:
let newStr = str.substring(to: index) // Swift 3
let newStr = String(str[..<index]) // Swift 4
let newStr = str.substring(from: index) // Swift 3
let newStr = String(str[index...]) // Swift 4
let range = firstIndex..<secondIndex // If you have a range
let newStr = = str.substring(with: range) // Swift 3
let newStr = String(str[range]) // Swift 4
Swift 5, 4
Usage
let text = "Hello world"
text[0] // H
text[...3] // "Hell"
text[6..<text.count] // world
text[NSRange(location: 6, length: 3)] // wor
Code
import Foundation
public extension String {
subscript(value: Int) -> Character {
self[index(at: value)]
}
}
public extension String {
subscript(value: NSRange) -> Substring {
self[value.lowerBound..<value.upperBound]
}
}
public extension String {
subscript(value: CountableClosedRange<Int>) -> Substring {
self[index(at: value.lowerBound)...index(at: value.upperBound)]
}
subscript(value: CountableRange<Int>) -> Substring {
self[index(at: value.lowerBound)..<index(at: value.upperBound)]
}
subscript(value: PartialRangeUpTo<Int>) -> Substring {
self[..<index(at: value.upperBound)]
}
subscript(value: PartialRangeThrough<Int>) -> Substring {
self[...index(at: value.upperBound)]
}
subscript(value: PartialRangeFrom<Int>) -> Substring {
self[index(at: value.lowerBound)...]
}
}
private extension String {
func index(at offset: Int) -> String.Index {
index(startIndex, offsetBy: offset)
}
}
Shorter in Swift 4/5:
let string = "123456"
let firstThree = String(string.prefix(3)) //"123"
let lastThree = String(string.suffix(3)) //"456"
Swift5
(Java's substring method):
extension String {
func subString(from: Int, to: Int) -> String {
let startIndex = self.index(self.startIndex, offsetBy: from)
let endIndex = self.index(self.startIndex, offsetBy: to)
return String(self[startIndex..<endIndex])
}
}
Usage:
var str = "Hello, Nick Michaels"
print(str.subString(from:7,to:20))
// print Nick Michaels
The conversion of your code to Swift 4 can also be done this way:
let str = "Hello, playground"
let index = str.index(of: ",")!
let substr = str.prefix(upTo: index)
You can use the code below to have a new string:
let newString = String(str.prefix(upTo: index))
substring(from: index) Converted to [index...]
Check the sample
let text = "1234567890"
let index = text.index(text.startIndex, offsetBy: 3)
text.substring(from: index) // "4567890" [Swift 3]
String(text[index...]) // "4567890" [Swift 4]
Some useful extensions:
extension String {
func substring(from: Int, to: Int) -> String {
let start = index(startIndex, offsetBy: from)
let end = index(start, offsetBy: to - from)
return String(self[start ..< end])
}
func substring(range: NSRange) -> String {
return substring(from: range.lowerBound, to: range.upperBound)
}
}
Example of uppercasedFirstCharacter convenience property in Swift3 and Swift4.
Property uppercasedFirstCharacterNew demonstrates how to use String slicing subscript in Swift4.
extension String {
public var uppercasedFirstCharacterOld: String {
if characters.count > 0 {
let splitIndex = index(after: startIndex)
let firstCharacter = substring(to: splitIndex).uppercased()
let sentence = substring(from: splitIndex)
return firstCharacter + sentence
} else {
return self
}
}
public var uppercasedFirstCharacterNew: String {
if characters.count > 0 {
let splitIndex = index(after: startIndex)
let firstCharacter = self[..<splitIndex].uppercased()
let sentence = self[splitIndex...]
return firstCharacter + sentence
} else {
return self
}
}
}
let lorem = "lorem".uppercasedFirstCharacterOld
print(lorem) // Prints "Lorem"
let ipsum = "ipsum".uppercasedFirstCharacterNew
print(ipsum) // Prints "Ipsum"
You can create your custom subString method using extension to class String as below:
extension String {
func subString(startIndex: Int, endIndex: Int) -> String {
let end = (endIndex - self.count) + 1
let indexStartOfText = self.index(self.startIndex, offsetBy: startIndex)
let indexEndOfText = self.index(self.endIndex, offsetBy: end)
let substring = self[indexStartOfText..<indexEndOfText]
return String(substring)
}
}
Creating SubString (prefix and suffix) from String using Swift 4:
let str : String = "ilike"
for i in 0...str.count {
let index = str.index(str.startIndex, offsetBy: i) // String.Index
let prefix = str[..<index] // String.SubSequence
let suffix = str[index...] // String.SubSequence
print("prefix \(prefix), suffix : \(suffix)")
}
Output
prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix :
If you want to generate a substring between 2 indices , use :
let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
I have written a string extension for replacement of 'String: subString:'
extension String {
func sliceByCharacter(from: Character, to: Character) -> String? {
let fromIndex = self.index(self.index(of: from)!, offsetBy: 1)
let toIndex = self.index(self.index(of: to)!, offsetBy: -1)
return String(self[fromIndex...toIndex])
}
func sliceByString(from:String, to:String) -> String? {
//From - startIndex
var range = self.range(of: from)
let subString = String(self[range!.upperBound...])
//To - endIndex
range = subString.range(of: to)
return String(subString[..<range!.lowerBound])
}
}
Usage : "Date(1511508780012+0530)".sliceByString(from: "(", to: "+")
Example Result : "1511508780012"
PS: Optionals are forced to unwrap. Please add Type safety check wherever necessary.
If you are trying to just get a substring up to a specific character, you don't need to find the index first, you can just use the prefix(while:) method
let str = "Hello, playground"
let subString = str.prefix { $0 != "," } // "Hello" as a String.SubSequence
When programming I often have strings with just plain A-Za-z and 0-9. No need for difficult Index actions. This extension is based on the plain old left / mid / right functions.
extension String {
// LEFT
// Returns the specified number of chars from the left of the string
// let str = "Hello"
// print(str.left(3)) // Hel
func left(_ to: Int) -> String {
return "\(self[..<self.index(startIndex, offsetBy: to)])"
}
// RIGHT
// Returns the specified number of chars from the right of the string
// let str = "Hello"
// print(str.left(3)) // llo
func right(_ from: Int) -> String {
return "\(self[self.index(startIndex, offsetBy: self.length-from)...])"
}
// MID
// Returns the specified number of chars from the startpoint of the string
// let str = "Hello"
// print(str.left(2,amount: 2)) // ll
func mid(_ from: Int, amount: Int) -> String {
let x = "\(self[self.index(startIndex, offsetBy: from)...])"
return x.left(amount)
}
}
Hope this will help little more :-
var string = "123456789"
If you want a substring after some particular index.
var indexStart = string.index(after: string.startIndex )// you can use any index in place of startIndex
var strIndexStart = String (string[indexStart...])//23456789
If you want a substring after removing some string at the end.
var indexEnd = string.index(before: string.endIndex)
var strIndexEnd = String (string[..<indexEnd])//12345678
you can also create indexes with the following code :-
var indexWithOffset = string.index(string.startIndex, offsetBy: 4)
with this method you can get specific range of string.you need to pass start index and after that total number of characters you want.
extension String{
func substring(fromIndex : Int,count : Int) -> String{
let startIndex = self.index(self.startIndex, offsetBy: fromIndex)
let endIndex = self.index(self.startIndex, offsetBy: fromIndex + count)
let range = startIndex..<endIndex
return String(self[range])
}
}
This is my solution, no warning, no errors, but perfect
let redStr: String = String(trimmStr[String.Index.init(encodedOffset: 0)..<String.Index.init(encodedOffset: 2)])
let greenStr: String = String(trimmStr[String.Index.init(encodedOffset: 3)..<String.Index.init(encodedOffset: 4)])
let blueStr: String = String(trimmStr[String.Index.init(encodedOffset: 5)..<String.Index.init(encodedOffset: 6)])
var str = "Hello, playground"
let indexcut = str.firstIndex(of: ",")
print(String(str[..<indexcut!]))
print(String(str[indexcut!...]))
You can try in this way and will get proper results.
the simples way that I use is :
String(Array(str)[2...4])
Swift 4, 5, 5+
Substring from Last
let str = "Hello World"
let removeFirstSix = String(str.dropFirst(6))
print(removeFirstSix) //World
Substring from First
let removeLastSix = String(str.dropLast(6))
print(removeLastSix) //Hello
Hope it would be helpful.
extension String {
func getSubString(_ char: Character) -> String {
var subString = ""
for eachChar in self {
if eachChar == char {
return subString
} else {
subString += String(eachChar)
}
}
return subString
}
}
let str: String = "Hello, playground"
print(str.getSubString(","))

How to solve a problem with using the method of branches and borders?

All words of the ternary language consist of only 3 letters: a, b, and c and all have a strictly specified length N. Words that do not contain two identical subsequences of letters in a row are considered correct. For example, abcacb is the correct word, and ababc is not the correct one, since the ab subsequences go there.
I tried to solve the problem with a complete enumeration of all possible combinations and a function that looked for a repeating sequence. However, this turned out to be the wrong decision. The problem needs to be solved somehow using the branch and bound method. I have absolutely no idea how this problem can be solved by this method. I would be very happy if someone provides examples or explains to me. I have already spent six days to solve this problem and am very tired.
My wrong solution:
import Foundation
func findRepetition(_ p: String) -> [String:Int] {
var repDict: [String:Int] = [:]
var p = p
while p.count != 0 {
for i in 0...p.count-1 {
repDict[String(Array(p)[0..<i]), default: 0] += 1
}
p = String(p.dropFirst())
}
return repDict
}
var correctWords = [String]()
var wrongWords = [String]()
func getRepeats(_ p: String) -> Bool {
let p = p
var a = findRepetition(p)
for i in a {
var substring = String(Array(repeating: i.key, count: 2).joined())
if p.contains(substring) {
wrongWords.append(p)
return false
}
}
correctWords.append(p)
return true
}
var counter = 0
func allLexicographicRecur (_ string: [String.Element], _ data: [String], _ last: Int, _ index: Int){
var length = string.count-1
var data = data
for i in 0...length {
data[index] = String(string[i])
if index == last {
if getRepeats(data.joined()) {
counter += 1
}
}else{
allLexicographicRecur(string, data, last, index+1)
}
}
}
func threeLanguage(_ l: Int) {
var alphabet = "abc"
var data = Array(repeating: "", count: l)
allLexicographicRecur(alphabet.sorted(), data, l-1, 0)
print("The specified word length: \(l), the number of correct words: \(counter)\n")
print("Correct words:\n\(correctWords)\n")
print("Wrong words:\n\(wrongWords)")
}
threeLanguage(3)
Example:
abca is the right word.
abab is wrong (ab).
aaaa is also wrong (a).
abcabc is also incorrect (abc).
If I correctly understood your problem, you need to separate you input string to parts N-length and check parts by your rules. Smth like this
let constant: Int = 3
extension String {
private func components(withLength length: Int) -> [String] {
return stride(from: 0, to: count, by: length).map {
let start = index(startIndex, offsetBy: $0)
let end = index(start, offsetBy: length, limitedBy: endIndex) ?? endIndex
return String(self[start ..< end])
}
}
var numberOfValidWords: Int {
var numberOfIncorrectWords = 0
let length = count - constant
let array = components(withLength: constant)
for component in array {
let computedLength = replacingOccurrences(of: component, with: "").count
if computedLength != length {
print("as is lengths are not equal, this part is met in string several times")
numberOfIncorrectWords += 1
continue
}
}
return array.count - numberOfIncorrectWords
}
}
Hope it will be helpful

In Swift, how to modify a character in string with subscript?

Like in C, we can simply do
str[i] = str[j]
But how to write the similar logic in swift?
Here is my code, but got error:
Cannot assign through subscript: subscript is get-only
let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)
targetString[indexI] = targetString[indexJ]
I know it may work by using this method, but it's too inconvenient
replaceSubrange(, with: )
In C, a string (char *) can be treated as an array of characters. In Swift, you can convert the String to an [Character], do the modifications you want, and then convert the [Character] back to String.
For example:
let str = "hello"
var strchars = Array(str)
strchars[0] = strchars[4]
let str2 = String(strchars)
print(str2) // "oello"
This might seem like a lot of work for a single modification, but if you are moving many characters this way, you only have to convert once each direction.
Reverse a String
Here's an example of a simple algorithm to reverse a string. By converting to an array of characters first, this algorithm is similar to the way you might do it in C:
let str = "abcdefg"
var strchars = Array(str)
var start = 0
var end = strchars.count - 1
while start < end {
let temp = strchars[start]
strchars[start] = strchars[end]
strchars[end] = temp
start += 1
end -= 1
}
let str2 = String(strchars)
print(str2) // "gfedcba"
Dealing with String with Swift is major pain in the a**. Unlike most languages I know that treat string as an array of characters, Swift treats strings as collection of extended grapheme clusters and the APIs to access them is really clumsy. Changes are coming in Swift 4 but that manifesto lost me about 10 paragraphs in.
Back to your question... you can replace the character like this:
var targetString = "Hello world"
let i = 0
let j = 1
let indexI = targetString.index(targetString.startIndex, offsetBy: i)
let indexJ = targetString.index(targetString.startIndex, offsetBy: j)
targetString.replaceSubrange(indexI...indexI, with: targetString[indexJ...indexJ])
print(targetString) // eello world
I was quite shocked as well by the fact that swift makes string indexing so damn complicated. For that reason, I have built some string extensions that enable you to retrieve and change parts of strings based on indices, closed ranges, and open ranges, PartialRangeFrom, PartialRangeThrough, and PartialRangeUpTo. You can download the repository I created here
You can also pass in negative numbers in order to access characters from the end backwards.
public extension String {
/**
Enables passing in negative indices to access characters
starting from the end and going backwards.
if num is negative, then it is added to the
length of the string to retrieve the true index.
*/
func negativeIndex(_ num: Int) -> Int {
return num < 0 ? num + self.count : num
}
func strOpenRange(index i: Int) -> Range<String.Index> {
let j = negativeIndex(i)
return strOpenRange(j..<(j + 1), checkNegative: false)
}
func strOpenRange(
_ range: Range<Int>, checkNegative: Bool = true
) -> Range<String.Index> {
var lower = range.lowerBound
var upper = range.upperBound
if checkNegative {
lower = negativeIndex(lower)
upper = negativeIndex(upper)
}
let idx1 = index(self.startIndex, offsetBy: lower)
let idx2 = index(self.startIndex, offsetBy: upper)
return idx1..<idx2
}
func strClosedRange(
_ range: CountableClosedRange<Int>, checkNegative: Bool = true
) -> ClosedRange<String.Index> {
var lower = range.lowerBound
var upper = range.upperBound
if checkNegative {
lower = negativeIndex(lower)
upper = negativeIndex(upper)
}
let start = self.index(self.startIndex, offsetBy: lower)
let end = self.index(start, offsetBy: upper - lower)
return start...end
}
// MARK: - Subscripts
/**
Gets and sets a character at a given index.
Negative indices are added to the length so that
characters can be accessed from the end backwards
Usage: `string[n]`
*/
subscript(_ i: Int) -> String {
get {
return String(self[strOpenRange(index: i)])
}
set {
let range = strOpenRange(index: i)
replaceSubrange(range, with: newValue)
}
}
/**
Gets and sets characters in an open range.
Supports negative indexing.
Usage: `string[n..<n]`
*/
subscript(_ r: Range<Int>) -> String {
get {
return String(self[strOpenRange(r)])
}
set {
replaceSubrange(strOpenRange(r), with: newValue)
}
}
/**
Gets and sets characters in a closed range.
Supports negative indexing
Usage: `string[n...n]`
*/
subscript(_ r: CountableClosedRange<Int>) -> String {
get {
return String(self[strClosedRange(r)])
}
set {
replaceSubrange(strClosedRange(r), with: newValue)
}
}
/// `string[n...]`. See PartialRangeFrom
subscript(r: PartialRangeFrom<Int>) -> String {
get {
return String(self[strOpenRange(r.lowerBound..<self.count)])
}
set {
replaceSubrange(strOpenRange(r.lowerBound..<self.count), with: newValue)
}
}
/// `string[...n]`. See PartialRangeThrough
subscript(r: PartialRangeThrough<Int>) -> String {
get {
let upper = negativeIndex(r.upperBound)
return String(self[strClosedRange(0...upper, checkNegative: false)])
}
set {
let upper = negativeIndex(r.upperBound)
replaceSubrange(
strClosedRange(0...upper, checkNegative: false), with: newValue
)
}
}
/// `string[...<n]`. See PartialRangeUpTo
subscript(r: PartialRangeUpTo<Int>) -> String {
get {
let upper = negativeIndex(r.upperBound)
return String(self[strOpenRange(0..<upper, checkNegative: false)])
}
set {
let upper = negativeIndex(r.upperBound)
replaceSubrange(
strOpenRange(0..<upper, checkNegative: false), with: newValue
)
}
}
}
Usage:
let text = "012345"
print(text[2]) // "2"
print(text[-1] // "5"
print(text[1...3]) // "123"
print(text[2..<3]) // "2"
print(text[3...]) // "345"
print(text[...3]) // "0123"
print(text[..<3]) // "012"
print(text[(-3)...] // "345"
print(text[...(-2)] // "01234"
All of the above works with assignment as well. All subscripts have getters and setters.
a new extension added,
since String conforms to BidirectionalCollection Protocol
extension String{
subscript(at i: Int) -> String? {
get {
if i < count{
let idx = index(startIndex, offsetBy: i)
return String(self[idx])
}
else{
return nil
}
}
set {
if i < count{
let idx = index(startIndex, offsetBy: i)
remove(at: idx)
if let new = newValue, let first = new.first{
insert(first, at: idx)
}
}
}
}
}
call like this:
var str = "fighter"
str[at: 2] = "6"

Swift find all occurrences of a substring

I have an extension here of the String class in Swift that returns the index of the first letter of a given substring.
Can anybody please help me make it so it will return an array of all occurrences instead of just the first one?
Thank you.
extension String {
func indexOf(string : String) -> Int {
var index = -1
if let range = self.range(of : string) {
if !range.isEmpty {
index = distance(from : self.startIndex, to : range.lowerBound)
}
}
return index
}
}
For example instead of a return value of 50 I would like something like [50, 74, 91, 103]
You just keep advancing the search range until you can't find any more instances of the substring:
extension String {
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}
let keyword = "a"
let html = "aaaa"
let indicies = html.indicesOf(string: keyword)
print(indicies) // [0, 1, 2, 3]
I know we aren't playing code golf here, but for anyone interested in a functional style one-line implementation that doesn't use vars or loops, this is another possible solution:
extension String {
func indices(of string: String) -> [Int] {
return indices.reduce([]) { $1.encodedOffset > ($0.last ?? -1) && self[$1...].hasPrefix(string) ? $0 + [$1.encodedOffset] : $0 }
}
}
Here are 2 functions. One returns [Range<String.Index>], the other returns [Range<Int>]. If you don't need the former, you can make it private. I've designed it to mimic the range(of:options:range:locale:) method, so it supports all the same features.
import Foundation
extension String {
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<String.Index>] {
// the slice within which to search
let slice = (range == nil) ? self[...] : self[range!]
var previousEnd = s.startIndex
var ranges = [Range<String.Index>]()
while let r = slice.range(
of: aString, options: options,
range: previousEnd ..< s.endIndex,
locale: locale
) {
if previousEnd != self.endIndex { // don't increment past the end
previousEnd = self.index(after: r.lowerBound)
}
ranges.append(r)
}
return ranges
}
public func allRanges(
of aString: String,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> [Range<Int>] {
return allRanges(of: aString, options: options, range: range, locale: locale)
.map(indexRangeToIntRange)
}
private func indexRangeToIntRange(_ range: Range<String.Index>) -> Range<Int> {
return indexToInt(range.lowerBound) ..< indexToInt(range.upperBound)
}
private func indexToInt(_ index: String.Index) -> Int {
return self.distance(from: self.startIndex, to: index)
}
}
let s = "abc abc abc abc abc"
print(s.allRanges(of: "abc") as [Range<String.Index>])
print()
print(s.allRanges(of: "abc") as [Range<Int>])
There's not really a built-in function to do this, but we can implement a modified Knuth-Morris-Pratt algorithm to get all the indices of the string we want to match. It should also be very performant as we don't need to repeatedly call range on the string.
extension String {
func indicesOf(string: String) -> [Int] {
// Converting to an array of utf8 characters makes indicing and comparing a lot easier
let search = self.utf8.map { $0 }
let word = string.utf8.map { $0 }
var indices = [Int]()
// m - the beginning of the current match in the search string
// i - the position of the current character in the string we're trying to match
var m = 0, i = 0
while m + i < search.count {
if word[i] == search[m+i] {
if i == word.count - 1 {
indices.append(m)
m += i + 1
i = 0
} else {
i += 1
}
} else {
m += 1
i = 0
}
}
return indices
}
}
Please check the following answer for finding multiple items in multiple locations
func indicesOf(string: String) -> [Int] {
var indices = [Int]()
var searchStartIndex = self.startIndex
while searchStartIndex < self.endIndex,
let range = self.range(of: string, range: searchStartIndex..<self.endIndex),
!range.isEmpty
{
let index = distance(from: self.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
func attributedStringWithColor(_ strings: [String], color: UIColor, characterSpacing: UInt? = nil) -> NSAttributedString {
let attributedString = NSMutableAttributedString(string: self)
for string in strings {
let indexes = self.indicesOf(string: string)
for index in indexes {
let range = NSRange(location: index, length: string.count)
attributedString.addAttribute(NSAttributedString.Key.foregroundColor, value: color, range: range)
}
}
guard let characterSpacing = characterSpacing else {return attributedString}
attributedString.addAttribute(NSAttributedString.Key.kern, value: characterSpacing, range: NSRange(location: 0, length: attributedString.length))
return attributedString
}
can be used as follows :
let message = "Item 1 + Item 2 + Item 3"
message.attributedStringWithColor(["Item", "+"], color: UIColor.red)
and gets the result
This could be done with recursive method. I used a numeric string to test it. It returns an optional array of Int, meaning it will be nil if no substring can be found.
extension String {
func indexes(of string: String, offset: Int = 0) -> [Int]? {
if let range = self.range(of : string) {
if !range.isEmpty {
let index = distance(from : self.startIndex, to : range.lowerBound) + offset
var result = [index]
let substr = self.substring(from: range.upperBound)
if let substrIndexes = substr.indexes(of: string, offset: index + distance(from: range.lowerBound, to: range.upperBound)) {
result.append(contentsOf: substrIndexes)
}
return result
}
}
return nil
}
}
let numericString = "01234567890123456789012345678901234567890123456789012345678901234567890123456789"
numericString.indexes(of: "3456")
I have tweaked the accepted answer so that case sensitivity can be configured
extension String {
func allIndexes(of subString: String, caseSensitive: Bool = true) -> [Int] {
let subString = caseSensitive ? subString : subString.lowercased()
let mainString = caseSensitive ? self : self.lowercased()
var indices = [Int]()
var searchStartIndex = mainString.startIndex
while searchStartIndex < mainString.endIndex,
let range = mainString.range(of: subString, range: searchStartIndex..<mainString.endIndex),
!range.isEmpty
{
let index = distance(from: mainString.startIndex, to: range.lowerBound)
indices.append(index)
searchStartIndex = range.upperBound
}
return indices
}
}

Find the Range of the Nth word in a String

What I want is something like
"word1 word2 word3".rangeOfWord(2) => 6 to 10
The result could come as a Range or a tuple or whatever.
I'd rather not do the brute force of iterating over the characters and using a state machine. Why reinvent the lexer? Is there a better way?
In your example, your words are unique, and you can use the following method:
let myString = "word1 word2 word3"
let wordNum = 2
let myRange = myString.rangeOfString(myString.componentsSeparatedByString(" ")[wordNum-1])
// 6..<11
As pointed out by Andrew Duncan in the comments below, the above is only valid if your words are unique. If you have non-unique words, you can use this somewhat less neater method:
let myString = "word1 word2 word3 word2 word1 word3 word1"
let wordNum = 7 // 2nd instance (out of 3) of "word1"
let arr = myString.componentsSeparatedByString(" ")
var fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + wordNum - 1
let myRange = Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count))
let myWord = myString.substringWithRange(myRange)
// string "word1" (from range 36..<41)
Finally, lets use the latter to construct an extension of String as you have wished for in your question example:
extension String {
private func rangeOfNthWord(wordNum: Int, wordSeparator: String) -> Range<String.Index>? {
let arr = myString.componentsSeparatedByString(wordSeparator)
if arr.count < wordNum {
return nil
}
else {
let fromIndex = arr[0..<wordNum-1].map { $0.characters.count }.reduce(0, combine: +) + (wordNum - 1)*wordSeparator.characters.count
return Range<String.Index>(start: myString.startIndex.advancedBy(fromIndex), end: myString.startIndex.advancedBy(fromIndex+arr[wordNum-1].characters.count))
}
}
}
let myString = "word1 word2 word3 word2 word1 word3 word1"
let wordNum = 7 // 2nd instance (out of 3) of "word1"
if let myRange = myString.rangeOfNthWord(wordNum, wordSeparator: " ") {
// myRange: 36..<41
print(myString.substringWithRange(myRange)) // prints "word1"
}
You can tweak the .rangeOfNthWord(...) method if word separation is not unique (say some words are separated by two blankspaces " ").
Also pointed out in the comments below, the use of .rangeOfString(...) is not, per se, pure Swift. It is, however, by no means bad practice. From Swift Language Guide - Strings and Characters:
Swift’s String type is bridged with Foundation’s NSString class. If
you are working with the Foundation framework in Cocoa, the entire
NSString API is available to call on any String value you create when
type cast to NSString, as described in AnyObject. You can also use a
String value with any API that requires an NSString instance.
See also the NSString class reference for rangeOfString method:
// Swift Declaration:
func rangeOfString(_ searchString: String) -> NSRange
I went ahead and wrote the state machine. (Grumble..) FWIW, here it is:
extension String {
private func halfOpenIntervalOfBlock(n:Int, separator sep:Character? = nil) -> (Int, Int)? {
enum State {
case InSeparator
case InPrecedingSeparator
case InWord
case InTarget
case Done
}
guard n > 0 else {
return nil
}
var state:State
if n == 1 {
state = .InPrecedingSeparator
} else {
state = .InSeparator
}
var separatorNum = 0
var startIndex:Int = 0
var endIndex:Int = 0
for (i, c) in self.characters.enumerate() {
let inSeparator:Bool
// A bit inefficient to keep doing this test.
if let s = sep {
inSeparator = c == s
} else {
inSeparator = c == " " || c == "\n"
}
endIndex = i
switch state {
case .InPrecedingSeparator:
if !inSeparator {
state = .InTarget
startIndex = i
}
case .InTarget:
if inSeparator {
state = .Done
}
case .InWord:
if inSeparator {
separatorNum += 1
if separatorNum == n - 1 {
state = .InPrecedingSeparator
} else {
state = .InSeparator
}
}
case .InSeparator:
if !inSeparator {
state = .InWord
}
case .Done:
break
}
if state == .Done {
break
}
}
if state == .Done {
return (startIndex, endIndex)
} else if state == .InTarget {
return (startIndex, endIndex + 1) // We ran off end.
} else {
return nil
}
}
func rangeOfWord(n:Int) -> Range<Index>? {
guard let (s, e) = self.halfOpenIntervalOfBlock(n) else {
return nil
}
let ss = self.startIndex.advancedBy(s)
let ee = self.startIndex.advancedBy(e)
return Range(start:ss, end:ee)
}
}
It's not really clear whether the string has to be considered divided in words by separators it may contains, or if you're just looking for a specific substring occurrence.
Anyway both cases could be addressed in this way in my opinion:
extension String {
func enumerateOccurencies(of pattern: String, _ body: (Range<String.Index>, inout Bool) throws -> Void) rethrows {
guard
!pattern.isEmpty,
count >= pattern.count
else { return }
var stop = false
var lo = startIndex
while !stop && lo < endIndex {
guard
let r = self[lo..<endIndex].range(of: pattern)
else { break }
try body(r, &stop)
lo = r.upperBound
}
}
}
You'll then set stop to true in the body closure once reached the desired occurrence number and capture the range passed to it:
let words = "word1, word1, word2, word3, word1, word3"
var matches = 0
var rangeOfThirdOccurencyOfWord1: Range<String.Index>? = nil
words.enumerateOccurencies(of: "word1") { range, stop in
matches +=1
stop = matches == 3
if stop {
rangeOfThirdOccurencyOfWord1 = range
}
}
Regarding the DFA: recently I've wrote one leveraging on Hashable and using a an Array of Dictionaries as its state nodes, but I've found that the method above is faster, cause maybe range(of:) uses finger-printing.
UPDATE
Otherwise you could also achieve that API you've mentioned in this way:
import Foundation
extension String {
func rangeOfWord(order: Int, separator: String) -> Range<String.Index>? {
precondition(order > 0)
guard
!isEmpty,
!separator.isEmpty,
separator.count < count
else { return nil }
var wordsSoFar = 0
var lo = startIndex
while let r = self[lo..<endIndex].range(of: separator) {
guard
r.lowerBound != lo
else {
lo = r.upperBound
continue
}
wordsSoFar += 1
guard
wordsSoFar < order
else { return lo..<r.lowerBound }
lo = r.upperBound
}
if
lo < endIndex,
wordsSoFar + 1 == order
{
return lo..<endIndex
}
return nil
}
}
let words = "word anotherWord oneMore lastOne"
if let r = words.rangeOfWord(order: 4, separator: " ") {
print(words[r])
} else {
print("not found")
}
Here order parameter refers to the nth order of the word in the string, starting from 1. I've also added the separator parameter to specify a string token to use for finding words in the string (it can also be defaulted to " " to be able to call the function without having to specify it).
Here's my attempt at an updated answer in Swift 5.5:
import Foundation
extension String {
func rangeOfWord(atPosition wordAt: Int) -> Range<String.Index>? {
let fullrange = self.startIndex..<self.endIndex
var count = 0
var foundAt: Range<String.Index>? = nil
self.enumerateSubstrings(in: fullrange, options: .byWords) { _, substringRange, _, stop in
count += 1
if count == wordAt {
foundAt = substringRange
stop = true // Stop the enumeration after the word range is found.
}
}
return foundAt
}
}
let lorem = "Morbi leo risus, porta ac consectetur ac, vestibulum at eros."
if let found = lorem.rangeOfWord(atPosition: 8) {
print("found: \(lorem[found])")
} else {
print("not found.")
}
This solution doesn't make a new array to contain the words so uses less memory (I have not tested but in theory it should use less memory). As much as possible, the build in method is used therefore less chance of bugs.
Swift 5 solution, which allows you to specify the word separator
extension String {
func rangeOfWord(atIndex wordIndex: Int) -> Range<String.Index>? {
let wordComponents = self.components(separatedBy: " ")
guard wordIndex < wordComponents.count else {
return nil
}
let characterEndCount = wordComponents[0...wordIndex].map { $0.count }.reduce(0, +)
let start = String.Index(utf16Offset: wordIndex + characterEndCount - wordComponents[wordIndex].count, in: self)
let end = String.Index(utf16Offset: wordIndex + characterEndCount, in: self)
return start..<end
}
}