Swift split substring based on word wrap - swift

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

Related

How to truncate a comma-separated value string with remainder count

I'm trying to achieve string truncate with "& more..." when string is truncated. I have this in picture:
Exact code minus text, in image:
func formatString() -> String {
let combinedLength = 30
// This array will never be empty
let strings = ["Update my profile", "Delete me", "Approve these letters"]
// In most cases, during a loop (no order of strings)
//let strings = ["Update", "Delete", "Another long word"]
let rangeNum = strings.count > 1 ? 2 : 1
let firstN = strings[0..<rangeNum]
// A sum of first 2 or 1
let actualLength = firstN.compactMap { $0.count }.reduce(0, +)
switch actualLength {
case let x where x <= combinedLength:
// It's safe to display all
return strings.map{String($0)}.joined(separator: ", ")
default:
if rangeNum == 2 {
if actualLength <= combinedLength {
return strings.first! + ", " + strings[1] + ", & \(strings.count - 2) more..."
}
return strings.first! + ", & \(strings.count - 1) more..."
}
// There has to be at least one item in the array.
return strings.first!
}
}
While truncateMode looks like a match, it's missing the , & n more... where n is the remainder.
My code may not be perfect but was wondering how to refactor. I feel there's a bug in there somewhere. I've not taken into consideration for larger screens: iPad where I would want to display more comma-separated values, I only look for the max 2 then display "& n more" depending on the size of the array.
Is there a hidden modifier for this? I'm using XCode 13.4.1, targeting both iPhone and iPad.
Edit:
The title is incorrect. I want to convert an array of strings into a comma-separated value string that's truncated using the function I have.

return "location" of variable in a string

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]

swift replacingOccurrences regular expression with number calculation

I have [size=some_number%]some_text[/size], and I want to replace it with
<font size="some_number*font_size">sometext</font>
where font_size is some int variableI know how to extract some_number and some_text with regular expression, but how can I do the multiplie calculation ? Is there a way to do it in swift justing using replacingOccurrences?
#"\[size=(d+)%\]([\s\S]*?)\[\/size\]"#
Is there a way to do it in swift justing using replacingOccurrences?
ICU regular expressions don't do math. You'll have to deal with these one at a time, doing the search, performing the calculation, and then doing the replace for that occurrence, repeating that in a loop. Loop in reverse to avoid index-shifting issues.
For instance:
var s = """
yo[size=6%]ooo[/size]heyho[size=10%]yyy[/size]ha
"""
let font_size = 10
let reg = try! NSRegularExpression(pattern: "\\[size=(\\d+)%\\]([\\s\\S]*?)\\[\\/size\\]", options: [])
let matches = reg.matches(in: s, options: [], range: NSRange(location: 0, length: s.utf16.count))
let rev = matches.reversed() // work backwards for replacement
for match in rev {
let r = match.range
let size = s[Range(match.range(at:1), in:s)!]
let text = s[Range(match.range(at:2), in:s)!]
let prefix = "<font size=\""
let num = String(Int(size)!*font_size)
let rest = "\">" + text + "</font>"
s = s.replacingCharacters(in: Range(r, in:s)!, with: prefix + num + rest)
}
print(s)
Now s is
yo<font size="60">ooo</font>heyho<font size="100">yyy</font>ha

Difficulty getting readLine() to work as desired on HackerRank

I'm attempting to submit the HackerRank Day 6 Challenge for 30 Days of Code.
I'm able to complete the task without issue in an Xcode Playground, however HackerRank's site says there is no output from my method. I encountered an issue yesterday due to browser flakiness, but cleaning caches, switching from Safari to Chrome, etc. don't seem to resolve the issue I'm encountering here. I think my problem lies in inputString.
Task
Given a string, S, of length N that is indexed from 0 to N-1, print its even-indexed and odd-indexed characters as 2 space-separated strings on a single line (see the Sample below for more detail).
Input Format
The first line contains an integer, (the number of test cases).
Each line of the subsequent lines contain a String, .
Constraints
1 <= T <= 10
2 <= length of S < 10,000
Output Format
For each String (where 0 <= j <= T-1), print S's even-indexed characters, followed by a space, followed by S's odd-indexed characters.
This is the code I'm submitting:
import Foundation
let inputString = readLine()!
func tweakString(string: String) {
// split string into an array of lines based on char set
var lineArray = string.components(separatedBy: .newlines)
// extract the number of test cases
let testCases = Int(lineArray[0])
// remove the first line containing the number of unit tests
lineArray.remove(at: 0)
/*
Satisfy constraints specified in the task
*/
guard lineArray.count >= 1 && lineArray.count <= 10 && testCases == lineArray.count else { return }
for line in lineArray {
switch line.characters.count {
// to match constraint specified in the task
case 2...10000:
let characterArray = Array(line.characters)
let evenCharacters = characterArray.enumerated().filter({$0.0 % 2 == 0}).map({$0.1})
let oddCharacters = characterArray.enumerated().filter({$0.0 % 2 == 1}).map({$0.1})
print(String(evenCharacters) + " " + String(oddCharacters))
default:
break
}
}
}
tweakString(string: inputString)
I think my issue lies the inputString. I'm taking it "as-is" and formatting it within my method. I've found solutions for Day 6, but I can't seem to find any current ones in Swift.
Thank you for reading. I welcome thoughts on how to get this thing to pass.
readLine() reads a single line from standard input, which
means that your inputString contains only the first line from
the input data. You have to call readLine() in a loop to get
the remaining input data.
So your program could look like this:
func tweakString(string: String) -> String {
// For a single input string, compute the output string according to the challenge rules ...
return result
}
let N = Int(readLine()!)! // Number of test cases
// For each test case:
for _ in 1...N {
let input = readLine()!
let output = tweakString(string: input)
print(output)
}
(The forced unwraps are acceptable here because the format of
the input data is documented in the challenge description.)
Hi Adrian you should call readLine()! every row . Here an example answer for that challenge;
import Foundation
func letsReview(str:String){
var evenCharacters = ""
var oddCharacters = ""
var index = 0
for char in str.characters{
if index % 2 == 0 {
evenCharacters += String(char)
}
else{
oddCharacters += String(char)
}
index += 1
}
print (evenCharacters + " " + oddCharacters)
}
let rowCount = Int(readLine()!)!
for _ in 0..<rowCount {
letsReview(str:String(readLine()!)!)
}

Split a double by dot to two numbers

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