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

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.

Related

Is there a simple way to merge values based on keys in Swift dictionaries?

I have a text divided into an array of strings where the user can tap on each word, adding the word's index (key) and the string (value) to a dictionary.
Now, if the user adds two or more words that are adjacent, I would like the concatenate the string and make them share one index.
My idea was to use a computed property that rearranges the array of strings based on the keys and values in the dictionary. So when the user taps on a word, the function should update the dictionary while checking if there are any adjacent indexes already added.
Example code:
let text = "This is a test for merging adjacent words that the user has selected."
//The text divided in separate words that can be tapped
var arrayOfString: [String] {
text.components(separatedBy: " ")
}
//If a user taps on a word it will be saved with its index
var userSelectedWords: [Int:String] = [2 : "a", 3 : "test", 4 : "for", 6 : "adjacent", 7 : "words", 9 : "the", 11 : "has"]
//Mapping all the keys into an array
var selectedKeys = userSelectedWords.map { $0.key }.sorted()
var indexToRemove = [Int]()
for i in 0..<selectedKeys - 1 {
//If the key has a value of one less that the succeding key, the words are adjacent
if selectedKeys[i] == selectedKeys[i + 1] - 1 {
indexToRemove.append(selectedKeys[i+1])
if let currentWord = userSelectedWords[selectedKeys[i]], let nextWord = userSelectedWords[selectedKeys[i + 1]] {
concatenatedString.append("\(currentWord) \(nextWord)")
}
}
}
print(indexToRemove)
//Prints: [3, 4, 7] which are the indexes that should be removed.
print(concatenatedString)
//Prints: ["a test", "test for", "adjacent words"]
/*
Here I'm stuck. If there are more than two words adjacent, the function will of
course continue the iteration and create a new item in the concatenatedString.
It feels like it starts to get way too complicated.
*/
I'd very much appreciate any input or help in this regard. Maybe I'm just looking at it the wrong way...
A functional approach would be to create an array of Range objects based upon the contiguous sorted keys, and then filter for those whose length was greater than 1
let text = "This is a test for merging adjacent words that the user has selected."
let allWords = text.components(separatedBy: " ")
let userSelectedWords = [2 : "a", 3 : "test", 4 : "for", 6 : "adjacent", 7 : "words", 9 : "the", 11 : "has"]
let result = userSelectedWords.keys
.sorted()
.reduce(into: [Range]()) { ranges, index in
if let range = ranges.last, range.endIndex == index {
ranges[ranges.count - 1] = range.startIndex ..< index + 1
} else {
ranges.append(index ..< index + 1)
}
}
.filter { $0.count > 1 }
.map { allWords[$0].joined(separator: " ") }
print(result) // ["a test for", "adjacent words"]

Can i compare two bytes values on basis of nearly equal to in swift

i have two values in bytes in two different variables . i want to perform a certain action whenever values are nearly equal to each other.
I there any method in swift in which i can perform any action on variables values nearly equal to.
If recommend me some code , tutorial or article to achieve this.
I am new to swift so please avoid down voting.
let string1 = "Hello World"
let string2 = "Hello"
let byteArrayOfString1: [UInt8] = string1.utf8.map{UInt8($0)} //Converting HELLO WORLD into Byte Type Array
let byteArrayOfString2: [UInt8] = string2.utf8.map{UInt8($0)} //Converting HELLO into Byte Type Array
if byteArrayOfString1 == byteArrayOfString2 {
print("Match")
}else {
print("Not Match")
}
For more Help, Visit https://medium.com/#gorjanshukov/working-with-bytes-in-ios-swift-4-de316a389a0c
well exactly i don't think so there is such method that compare approx values but if you discuss what exactly you want to do we can find a better alternative solution.
Here is the Solution:
func nearlyEqual(a: Float, b: Float, epsilon: Float) -> Bool {
let absA = abs(a)
let absB = abs(b)
let diff = abs(a - b)
if a == b {
return true
} else if (a == 0 || b == 0 || absA + absB < Float.leastNonzeroMagnitude) {
// a or b is zero or both are extremely close to it
// relative error is less meaningful here
return diff < (epsilon * Float.leastNonzeroMagnitude)
} else {
return diff / (absA + absB) < epsilon
}
}
Then you can use it like :
print(nearlyEqual(a: 1.2, b: 1.4, epsilon: 0.2))
This will return true.

Swift split substring based on word wrap

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

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

Format println output in a table

Like this Java question, but for Swift.
How can I output a table like this to the console, ideally using println?
n result1 result2 time1 time2
-----------------------------------------------------
5 1000.00 20000.0 1000ms 1250ms
5 1000.00 20000.0 1000ms 1250ms
5 1000.00 20000.0 1000ms 1250ms
I tried using println("n\tresult1\tresult2") but the results don't line up properly.
I found a quick and easy way to generate columnar text output in Swift (3.0) using the String method "padding(::)" [In Swift 2.x, the method is named "stringByPaddingToLength(::)"]. It allows you to specify the width of your column, the text you want to use as a pad, and the index of the pad to start with. Works like a charm if you don't mind that it only works with left-aligned text columns. If you want other alignments, you have to buy into the other methods of character counting and other such complexities.
The solution below is contrived to illustrate the utility of the method "padding(::)". Obviously, the best way to leverage this would be to create a function that iterated through a collection to produce the desired table while minimizing code repetition. I did it this way to focus on the task at hand.
Lastly, "println" doesn't seem to exist in Swift 2.x+, so I reverted to "print()".
To illustrate an example using your stated problem:
//Set up the data
let n : Int = 5
let result1 = 1000.0
let result2 = 20000.0
let time1 = "1000ms"
let time2 = "1250ms"
//Establish column widths
let column1PadLength = 8
let columnDefaultPadLength = 12
//Define the header string
let headerString = "n".padding(toLength: column1PadLength, withPad: " ", startingAt: 0) + "result1".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "result2".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "time1".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + "time2".padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0)
//Define the line separator
let lineString = "".padding(toLength: headerString.characters.count, withPad: "-", startingAt: 0)
//Define the string to display a line of our data
let nString = String(n)
let result1String = String(result1)
let result2String = String(result2)
let dataString = nString.padding(toLength: column1PadLength, withPad: " ", startingAt: 0) + result1String.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + result2String.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + time1.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0) + time2.padding(toLength: columnDefaultPadLength, withPad: " ", startingAt: 0)
//Print out the data table
print("\(headerString)\n\(lineString)\n\(dataString)")
The output will be printed to your console in a tidy columnar format:
n result1 result2 time1 time2
--------------------------------------------------------
5 1000.0 20000.0 1000ms 1250ms
Changing the variable "columnDefaultPadLength" from 12 to 8 will result in the following output:
n result1 result2 time1 time2
----------------------------------------
5 1000.0 20000.0 1000ms 1250ms
Finally, reducing the padding length to a value less than the data truncates the data instead of generating errors, very handy! Changing the "columnDefaultPadLength" from 8 to 4 results in this output:
n resuresutimetime
------------------------
5 1000200010001250
Obviously not a desired format, but with the simple adjustment of the padding length, you can quickly tweak the table into a compact yet readable form.
You need to determine the maximum length of a string in your data (from both the keys and values) and then pad those strings. You can use a function like what I've provided below to calculate the maximum length and go from there.
func maxLength(data: Dictionary<String,Double>) -> Int {
var greatestLength = 0
for (key, value) in data {
var valueLength = countElements(String(format: "%.2f", value))
var keyLength = countElements(key)
var length = max(valueLength, keyLength)
if (length > greatestLength) {
greatestLength = length
}
}
return greatestLength
}