Swift: How to make this function generic - swift

Here is what I have:
class func truncateTailsOfRange(range: Range<Int>, portion: Double) -> Range<Int> {
let start = range.startIndex + Int(portion * Double(range.count))
let end = range.endIndex - Int(portion * Double(range.count))
return Range(start: start, end: end)
}
I would like to make this generic for IntegerType:
class func truncateTailsOfRange<T: IntegerType>(range: Range<T>, portion: Double) -> Range<T> {
let start = range.startIndex + T(portion * Double(range.count))
let end = range.endIndex - T(portion * Double(range.count))
return Range(start: start, end: end)
}
But the error I get is:
Cannot invoke initializer for type Double with an argument list of type (T.Distance)
Is this possible to do?

First you need a CustomDoubleConvertible protocol. This mirrors CustomStringConvertible. You extend all Types which you want to be convertible to Double. Similar to description which returns a String representation of a Type.
protocol CustomDoubleConvertible {
var doubleValue : Double { get }
}
extension Int : CustomDoubleConvertible {
var doubleValue : Double { return Double(self) }
}
extension Int16 : CustomDoubleConvertible {
var doubleValue : Double { return Double(self) }
}
If you make the function an extension to Range itself you can make use of it's generic nature and it's typealiases.
extension Range where Element.Distance : CustomDoubleConvertible {
Now you can calculate the offsets of the indexes like so :
let startOffset = Int(portion * self.count.doubleValue)
let endOffset = Int(portion * self.count.doubleValue)
If you further constrain Range so that it's Element must be a BidirectionalIndexType you can use successor and predecessor.
extension Range where Element.Distance : CustomDoubleConvertible, Element : BidirectionalIndexType {
This allows you to get the full function by iterating over the offsets and calling successor and predecessor.
extension Range where Element.Distance : CustomDoubleConvertible, Element : BidirectionalIndexType {
func truncateTailsOfRange(portion: Double) -> Range<Element> {
let startOffset = Int(portion * self.count.doubleValue)
let endOffset = Int(portion * self.count.doubleValue)
var start = self.startIndex
var end = self.endIndex
for _ in 0..<startOffset { start = start.successor() }
for _ in 0..<endOffset { end = end.predecessor() }
return Range(start: start, end: end)
}
}
Some tests :
let rangeA = 1...4 // 1..<5
let rangeB = "a"..."g"
rangeA.truncateTailsOfRange(0.3) // 2..<4
rangeB.truncateTailsOfRange(0.3) // has no member ....
let w : Int16 = 3
let z : Int16 = 9
let rangeC = w...z // 3..<10
rangeC.truncateTailsOfRange(0.4) // 5..<8

This is an interesting but academic exercise.
Here's a method that is not very efficient but that will work with all range types:
func truncateTailsOfRange<T>(var range: Range<T>, portion: Double) -> Range<T>
{
let elementCount = Array(range).count
let truncationCount = Int( portion * Double(elementCount) )
let remainingCount = max(0, elementCount - 2 * truncationCount)
for _ in 0..<truncationCount
{ range.startIndex = range.startIndex.successor() }
range.endIndex = range.startIndex
for _ in 0..<remainingCount
{ range.endIndex = range.endIndex.successor() }
return range
}
and here's a much quicker one :
func truncateTailsOfRange2<T>(var range: Range<T>, portion: Double) -> Range<T>
{
if range.isEmpty {return range}
let elements = Array(range)
let truncationCount = Int( portion * Double(elements.count) )
let remainingCount = max(0, elements.count - 2 * truncationCount)
return elements[truncationCount]..<elements[truncationCount+remainingCount]
}

Related

Convert into generic function

I am writing an iOS app in Swift. My class has below functions for reading bytebuffer. Below all functions do same task but work on different number types. So, how to convert these functions into a single function using generics?
var array = [UInt8]()
private var currentIndex: Int = 0
private let hostEndianness: Endianness = OSHostByteOrder() == OSLittleEndian ? .little : .big
private var currentEndianness: Endianness = .big
public func getUInt16() -> UInt16 {
let check=(currentIndex + MemoryLayout<UInt16>.size) - 1
if array.indices.contains(check){
let result = from(Array(array[currentIndex..<currentIndex + MemoryLayout<UInt16>.size]), UInt16.self)
currentIndex += MemoryLayout<UInt16>.size
return result.littleEndian
}else{
return 0
}
}
public func getInt32() -> Int32 {
let check=(currentIndex + MemoryLayout<Int32>.size) - 1
if array.indices.contains(check){
let result = from(Array(array[currentIndex..<currentIndex + MemoryLayout<Int32>.size]), Int32.self)
currentIndex += MemoryLayout<Int32>.size
return result.littleEndian
}else{
return 0
}
}
public func getFloat() -> Float {
let check=(currentIndex + MemoryLayout<UInt32>.size) - 1
if array.indices.contains(check){
let result = from(Array(array[currentIndex..<currentIndex + MemoryLayout<UInt32>.size]), UInt32.self)
currentIndex += MemoryLayout<UInt32>.size
return Float(bitPattern: result.littleEndian)
}else{
return 0.0
}
}
public func getDouble() -> Double {
let check=(currentIndex + MemoryLayout<UInt64>.size) - 1
if array.indices.contains(check){
let result = from(Array(array[currentIndex..<currentIndex + MemoryLayout<UInt64>.size]), UInt64.self)
currentIndex += MemoryLayout<UInt64>.size
return Double(bitPattern: result.bigEndian)
}else{
return 0.0
}
}
The only difference in the above functions is types: Float, double, UInt16, Int32.
From Function:
private func from<T>(_ value: [UInt8], _: T.Type) -> T {
return value.withUnsafeBytes {
$0.load(fromByteOffset: 0, as: T.self)
}
}
My Solution:
public func getNumber<T:Numeric>() -> T {
let check=(currentIndex + MemoryLayout<T>.size) - 1
if array.indices.contains(check){
let result = from(Array(array[currentIndex..<currentIndex + MemoryLayout<T>.size]), T.self)
currentIndex += MemoryLayout<T>.size
return result.bigEndian
}else{
return 0
}
}
Error:
Value of type 'T' has no member 'bigEndian'

Rotate Swift Array

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)) //λͺ¨λͺΈλ§ˆλ§˜λ«„뫔ㅁ

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

Advance all elements in a Range in Swift?

If I have a Range, say
let bilbo = ( 1 ... 5 )
And I wanted to advance all elements of it by a number, say 3, is there a way other than
let offset = 3
let baggins = ( bilbo.first! + offset ... bilbo.last! + offset )
Something like
bilbo.advance_by( 3 )
Which I discovered only works for one element in the Range?
I have searched the web and SO and can't find an answer. I'm guessing that there is a Swift-ish way to do this that probably appears in another SO post somewhere that I just don't comprehend how it connects yet. Any help would be appreciated.
let advanceRangeBy : (Range<Int>, Int) -> Range<Int> = { $0.0.first!.advancedBy($0.1) ... $0.0.last!.advancedBy($0.1) }
let bilbo = 1...5
let bagger = advanceRangeBy(bilbo, 3) // 4..<9
You can also make it a generic extension to Range that will work for many (although not all) types of Range:
let bilbo = 1...5
extension Range where Element : BidirectionalIndexType {
func advanceRangeBy(advance: Element.Distance) -> Range<Element> {
return first!.advancedBy(advance) ... last!.advancedBy(advance)
}
}
let baggins = bilbo.advanceRangeBy(3)
For the sake of completeness, I thought I'd add that you can also perform this range advancement/de-advancement operation using a custom binary infix operator
infix operator <> {
associativity left
precedence 140 /* use same precedence as '+', '-' arithmetic */
}
func <> (lhs: Range<Int>, rhs: Int) -> Range<Int>{
var out : Range<Int> = lhs
out.endIndex = lhs.endIndex + rhs
out.startIndex = lhs.startIndex + rhs
return out
}
let bilbo = 1...5
let bagger = bilbo <> 3 // 4..<9
let frodo = bagger <> (-2) // 2..<7
How about a simple extension:
extension Range {
public func advancedBy(n: Element.Distance) -> Range<Element> {
let startIndex = self.startIndex.advancedBy(n)
let endIndex = self.endIndex.advancedBy(n)
return Range(start: startIndex, end: endIndex)
}
}
Update for swift 5:
public extension CountableRange {
func advanced(by n: Bound.Stride) -> Self {
let lowerBound = self.lowerBound.advanced(by: n)
let upperBound = self.upperBound.advanced(by: n)
return .init(uncheckedBounds: (lowerBound, upperBound))
}
static func + (lhs: Self, rhs: Bound.Stride) -> Self {
lhs.advanced(by: rhs)
}
}
Note that CountableRange is just a typealias for Range with conditions:
typealias CountableRange<Bound> = Range<Bound> where Bound : Strideable, Bound.Stride : SignedInteger
Use map:
let bagger = bilbo.map { $0.advancedBy(3) }