This is a bit on an odd ball question and I am not sure if it is possible to do, none the less.
I am trying to identify the "count" position of an item within a string.
For instance if I have a string: "hello what a lovely day" (23 characters) and I would like to know where in the sting the spaces are. In this case the sting would have a space at the 6th, 11th, 13th and 20th characters. Is there a function that would provide this feedback?
Any insight would be appreciated.
Thank you in advance for your valued time and insight.
Try this extension:
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
}
}
Usage (note that in Swift, nth characters start at 0 and not 1):
let string = "hello what a lovely day"
let indices = string.indicesOf(string: " ")
print("Indices are \(indices)")
Indices are [5, 10, 12, 19]
Related
junior developer here. I am currently trying to achieve a substring that is split every n characters of a String.
This is my code for the function
public func split(every: Int) -> [String] {
var result = [String]()
for i in stride(from: 0, to: self.count, by: every) {
let startIndex = self.index(self.startIndex, offsetBy: i)
let endIndex = self.index(startIndex, offsetBy: every, limitedBy: self.endIndex) ?? self.endIndex
result.append(String(self[startIndex..<endIndex]))
}
return result
}
The above code works as expected. But there is one lacking from the code above, which is the word wrapping. Here is the sample String
let itemName = "Japanese Matcha SM w RB -L Special Edition And Americano MS w Brown Sugar Limited Edition"
print(itemName.split(every: 26))
The result will be
["Japanese Matcha SM w RB -L", " Special Edition And Ameri", "cano MS w Brown Sugar Limi", "ted Edition"]
Notice the
[" Special Edition And Ameri"], ["cano MS w Brown Sugar Limi"]
I am trying to figure out how to do the word wrap algorithm based on every n character, but couldn't find any clue.
For example, from above case, how to generate the array becomes,
[" Special Edition And"], ["Americano MS w Brown"], ["Sugar"]
So as you can see, the algorithm might check whether every n characters has a word that is being cut out (dynamic check based on the n characters), hence will move the cut word into the next array.
So in that case, the algorithm will cleverly bypass the every n character, might be less, but not more than n characters, if there is any word not being wrapped.
Is my explanation clear? Can anyone guide me please? Thanks
This is some simple implementation of this algorithm, you can start with that.
First we cut string by words, then add them to temporary string until we meet characters limit.
let itemName = "Japanese Matcha SM w RB -L Special Edition And Americano MS w Brown Sugar Limited Edition"
let table = itemName.split(separator: " ")
let limit = 26
var tempString = ""
var finalResult: [String] = []
for item in table {
tempString += item + " "
if tempString.count >= limit {
finalResult.append(tempString)
tempString = ""
}
}
print(finalResult)
How about this?
extension String {
func split(every: Int) -> [String] {
var result = [String]()
let words = self.split(separator: " ")
var line = String(words.first!)
words.dropFirst().forEach { word in
let word = " " + String(word)
if line.count + word.count <= every {
line.append(word)
} else {
result.append(line)
line = word
}
}
result.append(line)
return result
}
}
I've come outwith this code to remove two charahcters from sentence, however I was wondering how to start counting sentence from 1 when removing characters.
Examplre user enters in each textfield as following:
textfield.text: Hi, thankyou
inputlabelOne.text: 2
inputLabelTwo.text: 5
My code:
var numberOne = Int (InputLabelOne.text)!
var numberTwo = Int (InputLabelTwo.text)!
var text = textfield.text!
var num1 = numberOne
var num2 = numberTwo
if let oneIndex = text.index ((text.startIndex), offsetBy:
num1, limetedBy:(text.endIndex)) ,
let twoIndex = text.index ((text.startIndex), offsetBy: num2,
limetedBy:(text.endIndex)) {
text.remove(at: oneIndex)
text.remove(at: twoIndex)
outputLabel.Text = "sentence with removed letters: \(text)"
}
Firstly, you need to get first index of the string
let startIndex = string.startIndex
Next, you need to get String.Index of the character at first position
let index1 = string.index(startIndex, offsetBy: num1 - 1)
I type minus one because first character has 0 index
Next, you can remove this character
str.remove(at: index1)
Same for the second character
let offset = num1 > num2 ? 1 : 2
let index2 = string.index(startIndex, offsetBy: num2 - offset)
str.remove(at: index2)
If num1 is less than num2 then offset value is 2 because we have already removed one character.
If num1 is greater than num2 then offset value is 1 as for num1.
To avoid the mutating while iterating mistake you have to remove the characters backwards starting at the highest index.
To get 1-based indices just subtract 1 from the offset respectively
var text = "Hi, thankyou"
let inputLabelOne = 2
let inputLabelTwo = 5
if let oneIndex = text.index(text.startIndex, offsetBy: inputLabelOne - 1, limitedBy: text.endIndex),
let twoIndex = text.index(text.startIndex, offsetBy: inputLabelTwo - 1, limitedBy: text.endIndex) {
text.remove(at: twoIndex)
text.remove(at: oneIndex)
}
print(text) // H, hankyou
or as function
func remove(at indices: [Int], from text: inout String) {
let sortedIndices = indices.sorted(by: >)
for index in sortedIndices {
if let anIndex = text.index(text.startIndex, offsetBy: index - 1, limitedBy: text.endIndex) {
text.remove(at: anIndex)
}
}
}
remove(at: [inputLabelOne, inputLabelTwo], from: &text)
If you create a var Range = 0...0, I would expect the endIndex to be zero. But in reality is 1.
var myRange: Range<Int> = 0...0
print("start Index \(myRange.startIndex) End Index \(myRange.endIndex)")
output: "start Index 0 End Index 1"
How can I question a Range instance if an Index of type Int is contained ?
The endIndex is not actually included in the Range. The Range is startIndex ..< endIndex. So, for your example, 0...0 is stored as 0..<1 which means the same thing.
For Swift 1.2 you can use the global function contains to check if an Int is contained by a Range:
var myRange: Range<Int> = 0...0
let i: Int = 1
if contains(myRange, i) {
println("yes")
} else {
println("no") // prints "no"
}
For Swift 2.0:
var myRange: Range<Int> = 0...0
let i: Int = 1
if myRange.contains(i) {
print("yes")
} else {
print("no") // prints "no"
}
Maybe you could refer to Half-Open Range Operator
var myRange: Range<Int> = 0..<0
outputs:"start Index 0 End Index 0"
The half-open range operator (a..<b) defines a range that runs from a to b, but does not include b. And the closed range operator (a...b) will finally turn to (a..<b+1)
Because Range is also a collection, you can use its minElement() and maxElement() methods, which will return the correct index, respecting the range being closed (...) or half-open (..<).
So the below code will output zeros as expected:
let range: Range<Int> = 0...0
let min = range.minElement()!
let max = range.maxElement()!
print("min=\(min), max=\(max)")
// Output: "min=0, max=0"
Note: both methods have O(elements.count) complexity which might not be suitable for some cases.
So I am trying to split a number in swift, I have tried searching for this on the internet but have had no success. So first I will start with a number like:
var number = 34.55
And from this number, I want to create two separate number by splitting from the dot. So the output should be something like:
var firstHalf = 34
var secondHalf = 55
Or the output can also be in an array of thats easier. How can I achieve this?
The easiest way would be to first cast the double to a string:
var d = 34.55
var b = "\(d)" // or String(d)
then split the string with the global split function:
var c = split(b) { $0 == "." } // [34, 55]
You can also bake this functionality into a double:
extension Double {
func splitAtDecimal() -> [Int] {
return (split("\(self)") { $0 == "." }).map({
return String($0).toInt()!
})
}
}
This would allow you to do the following:
var number = 34.55
print(number.splitAtDecimal()) // [34, 55]
Well, what you have there is a float, not a string. You can't really "split" it, and remember that a float is not strictly limited to 2 digits after the separator.
One solution is :
var firstHalf = Int(number)
var secondHalf = Int((number - firstHalf) * 100)
It's nasty but it'll do the right thing for your example (it will, however, fail when dealing with numbers that have more than two decimals of precision)
Alternatively, you could convert it into a string and then split that.
var stringified = NSString(format: "%.2f", number)
var parts = stringifed.componentsSeparatedByString(".")
Note that I'm explicitly calling the formatter here, to avoid unwanted behavior of standard float to string conversions.
Add the following extension:
extension Double {
func splitIntoParts(decimalPlaces: Int, round: Bool) -> (leftPart: Int, rightPart: Int) {
var number = self
if round {
//round to specified number of decimal places:
let divisor = pow(10.0, Double(decimalPlaces))
number = Darwin.round(self * divisor) / divisor
}
//convert to string and split on decimal point:
let parts = String(number).components(separatedBy: ".")
//extract left and right parts:
let leftPart = Int(parts[0]) ?? 0
let rightPart = Int(parts[1]) ?? 0
return(leftPart, rightPart)
}
Usage - Unrounded:
let number:Double = 95.99999999
let parts = number.splitIntoParts(decimalPlaces: 3, round: false)
print("LeftPart: \(parts.leftPart) RightPart: \(parts.rightPart)")
Outputs:
LeftPart: 95 RightPart: 999
Usage Rounded:
let number:Double = 95.199999999
let parts = number.splitIntoParts(decimalPlaces: 1, round: true)
Outputs:
LeftPart: 95 RightPart: 2
Actually, it's not possible to split a double by dot into two INTEGER numbers. All earlier offered solutions will produce a bug in nearly 10% of cases.
Here's why:
The breaking case will be numbers with decimal parts starting with one or more zeroes, for example: 1.05, 11.00698, etc.
Any decimal part that starts with one or more zeroes will have those zeroes discarded when converted to integers. The result of those conversions:
1.05 will become (1, 5)
11.00698 will become (11, 698)
An ugly and hard to find bug is guaranteed...
The only way to meaningfully split a decimal number is to convert it to (Int, Double). Below is a simple extension to Double that does that:
extension Double {
func splitAtDecimal() -> (Int, Double) {
let whole = Int(self)
let decimal = self - Darwin.floor(self)
return (whole, decimal)
}
}
Imagine we have an arbitrary Range<T> and we want to create a new range with startIndex and endIndex advanced by 50 units.
My first thought was to do this:
let startIndex = advance(range.startIndex, 50)
let endIndex = advance(range.endIndex, 50)
var newRange = startIndex..<endIndex
But this gives "fatal error: can not increment endIndex". (Well, it does with Range<String.Index>. I haven't tried it with other generic parameters.) I've tried quite a few permutations of this, including assigning range.startIndex and range.endIndex to new variables, etc. Nothing works.
Let me stress that I'm looking for a solution that works with any T. GoZoner gave an answer below that I haven't tried with Int, but I wouldn't be surprised if it worked. However, no permutation of it I tried will work when T is String.Index
So, how can I do this?
There’s a second version of advance that takes a maximum index not to go beyond:
let s = "Hello, I must be going."
let range = s.startIndex..<s.endIndex
let startIndex = advance(range.startIndex, 50, s.endIndex)
let endIndex = advance(range.endIndex, 50, s.endIndex)
var newRange = startIndex..<endIndex
if newRange.isEmpty {
println("new range out of bounds")
}
Try:
1> var r1 = 1..<50
r1: Range<Int> = 1..<50
2> var r2 = (r1.startIndex+50)..<(r1.endIndex+50)
r2: Range<Int> = 51..<100