Convert/Split string to array at letter - swift

I know there are a lot of other threads for splitting a String by a specific separator or position. I have a little bit different case and couldn't find any similar question here.
My input string is:
"A 300 133 Z 800 900 12 50 Q 3 -10 X"
and the desired output is:
[["A", "300", "133"], ["Z", "800", "900", "12", "50"], ["Q", "3", "-10"], ["X"]]

First, let's separate each elements:
let inputString = "A 300 133 Z 800 900 12 50 Q 3 -10 X"
let elements = inputString.components(separatedBy: .whitespaces)
Then, we can use reduce(into:_:): we iterate each element of the array. If it's a letter, we append it as a new sub array, if not, we append it to the last sub array.
let reduced = elements.reduce(into: [[String]]()) { result, current in
guard var last = result.last,
current.rangeOfCharacter(from: .letters) == nil else {
result.append([current])
return
}
last.append(current)
result[result.count - 1] = last
}
Side note:
I used the test "is letter": "has a letter", so if you have "333D", it will return "true". The test there is to update according to your needs, I don't check if there is only one letter (as in your example, etc).

Related

LeetCode 249. Group Shifted Strings

I am solving the leetcode question 249. Group Shifted Strings. Can someone please explain how the keys are stored in the hashMap?
We can shift a string by shifting each of its letters to its successive letter.
For example, "abc" can be shifted to be "bcd".
We can keep shifting the string to form a sequence.
For example, we can keep shifting "abc" to form the sequence: "abc" -> "bcd" -> ... -> "xyz".
Given an array of strings strings, group all strings[i] that belong to the same shifting sequence. You may return the answer in any order.
Input: strings = ["abc","bcd","acef","xyz","az","ba","a","z"]
Output: [["acef"],["a","z"],["abc","bcd","xyz"],["az","ba"]]
func groupStrings(_ strings: [String]) -> [[String]] {
var dict = [[Int]: [String]]()
for word in strings {
var key = [0]
if word.count > 1 {
var a = Array(word)
let pivot = Int(a[0].asciiValue!) - 97
for i in 1..<a.count {
let index = (Int(a[i].asciiValue!) - 97 + 26 - pivot) % 26
key.append(index)
}
}
if var array = dict[key] {
array.append(word)
dict[key] = array
} else {
dict[key] = [word]
}
}
return dict.keys.compactMap { dict[$0] }
}
"[[0, 2, 4, 5]: ["acef"], [0, 25]: ["az", "ba"], [0]: ["a", "z"], [0, 1, 2]: ["abc", "bcd", "xyz"]]"
Your question is not much clear but I believe u are requesting an explanation to the groupStrings function.
In your question u have to find the shifting sequences. eg : abc -> bcd -> cde ..... -> xyz.
So the logic behind this is checking the ASCII values of each character. Because if two strings form a shifting sequence the differences between the ASCII values of any consecutive pair of their characters are the same.
For an example we know abc bcd xyz are in a shifting sequence`.
Characters ASCCI values Difference from the 1st value
a b c 97 98 99. 0 1 2
b c d 98 99 100. 0 1 2
x y z 120 121 122. 0 1 2
Basically what happening in your function is creating a dictionary which contains those differences as keys and strings as values.
func groupStrings(_ strings: [String]) -> [[String]] {
var dict = [[Int]: [String]]() // initialize the dictionary
for word in strings {
var key = [0]
if word.count > 1 {
var a = Array(word) //create an array from the charachters of the word
let pivot = Int(a[0].asciiValue!) - 97 // get the ASCII value of the 1st lestter
// we substract 97 because for english letters ASCII values start from 97.
// by substracting 97 we can map the ASCCII of a to 0, b to 1 .... z to 26
for i in 1..<a.count {
// calulate the differences between the ASCII values of any consecutive pairs
let index = (Int(a[i].asciiValue!) - 97 + 26 - pivot) % 26
key.append(index)
}
}
// here check if there is an already a difference sequence in the dictionary
//if true we append the word to that exsiting key value pair
//else create a new key value pair
if var array = dict[key] {
array.append(word)
dict[key] = array
} else {
dict[key] = [word]
}
}
return dict.keys.compactMap { dict[$0] }
}

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.

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"]

swift 3 alphanumeric sorting re: I, II, III, IV, IX, V,

Curious to know if anyone can offer a solution to sorting roman numerals (string type) I through X. When I sort an array using {$0.compare ($1, options: .numeric) == .orderedAscending}, I get I, II, III, IV, IX, V, VI, VII, VIII X. As you can see, IX follows IV because of the "I."
By the way, the data model is a dictionary [String:[String:[String]]] The Bold indicates where in the dictionary the data to be sorted exists.
Is this what you mean? Perhaps by converting them first
let romanValues = ["M", "CM", "D", "CD", "C", "XC", "L", "XL", "X", "IX", "V", "IV", "I"]
let arabicValues = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
var romanValue = ""
var startingValue = number
for (index, romanChar) in enumerate(romanValues) {
var arabicValue = arabicValues[index]
var div = startingValue / arabicValue
if (div > 0)
{
for j in 0..<div
{
//println("Should add \(romanChar) to string")
romanValue += romanChar
}
startingValue -= arabicValue * div
}
}
return romanValue
I came up with a simple solution. Rather than hassle with the key of the dictionary to populate the rows of a table view in the desired numeric order, I created a simple array- which is ordered. When a row is selected, I can use didSelectRow to identify the indexPath and the related string of the array- for example row 1 = "Article I". I can then pass the selected string value "Article I" as a variable to use in the selection of the identical key [String:[String:[String]]] within the nested dictionary.
PS - the solution posted by Lucas appears to be a partial copy and paste of a function that can be found on GitHub. It appears Lucas inadvertently failed to copy and paste the entire function.

map function in Swift converting String to Int?

let digitNames = [
0: "Zero", 1: "One", 2: "Two", 3: "Three", 4: "Four",
5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output
number /= 10
}
return output
}
// strings is inferred to be of type [String]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]
I was hoping if there is anyone that could explain how this code works (it is taken from apple's developer page for swift under "closures). I'm not too sure especially what the code in the "while" loop means :/ how exactly is the number converted to string?
map function is Higher Order Level function and it is used to do some operation on single elements of array and return a transformed array generated after your operations.
numbers.map will traverse each element of array and transform the elements by doing some operation and returned a transformed array .
output = digitNames[number % 10]! + output
1) for first element in array 16 in first iteration of while loop number % 10 will return 6 as a reminder of 16 after dividing by 10 so digitName[6] will assign output to Six
let strings = numbers.map {
(var number) -> String in
var output = ""
while number > 0 {
output = digitNames[number % 10]! + output //16
number /= 10
}
return output
}
2) It divides number by 10 and which will give 1 now number will be 1
3) while number > 0 { checks if number is greater than 0 yes it is 1
4) Again iterate now this time digitNames[number % 10]! return One and by appending previous output it will become One append output(which is Six).So OneSix
Your first element become OneSix.This will done for each element and after all elements map return String array.So finally String become
["OneSix", "FiveEight", "FiveOneZero"]