I found this example function that takes a integer and then sorts the number from largest to smallest. Example input: 12345 output: 54321. The solution works but I am trying to understand the String.init part. Could anyone explain what is going on?
func descendingOrder(of number: Int) -> Int {
let s = String(number).sorted().reversed()
return Int(s.compactMap(String.init).joined())!
}
s is of type ReversedCollection<[String.Element]> so it makes the conversion from String.Element to String this is the meaning of String.init inside loop of compactMap
When you see DataType.init you need to know that the item being looped is a paramter for the init of that type so loop occurs over the characters of the String s to make a new [String] as a result
Developer complicates the line , it could be simplified to this for netter understanding
func descendingOrder(of number: Int) -> Int {
let s = String(number).sorted().reversed() // sort and reverse the string descendinng
let arr = s.compactMap(String.init) // convert characters to array of strings
let joined = arr.joined() // join them in 1 string
let res = Int(joined)! // convert string to int
return res
}
OR as in comments
func descendingOrder(of number: Int) -> Int {
let s = String(number).sorted().reversed() // sort and reverse the string descendinng
let arr = s.map{ String($0) } // convert characters to array of strings
let joined = arr.joined() // join them in 1 string
let res = Int(joined)! // convert string to int
return res
}
Related
I'm removing accent characters from the Polish Alphabet when searching through a database. That way the user can type in text without accent.
I'm using this in my TableView search controller with approx 15,000 Strings. The code works but it is very slow, app freezes for a second with every letter typed.
Does anyone have a solution for more efficient approach?
My Filter for the TableView:
//My old method which didn't convert accent letters and works smoothly
var arr = dataSetArray.filter({$0.lowercased().contains(searchText.lowercased())})
//My new filtering method
var arr = dataSetArray.filter({$0.forSorting().contains(searchText.lowercased())})
My Extension:
extension String {
func forSorting() -> String {
let set = [("ą", "a"), ("ć", "c"), ("ę", "e"), ("ł", "l"), ("ń", "n"), ("ó", "o"), ("ś", "s"), ("ź", "z"), ("ż", "z")]
let ab = self.lowercased()
let new = ab.folding(options: .diacriticInsensitive, locale: nil)
let final = new.replaceCharacters(characters: set)
return final
}
}
extension String {
func replaceCharacters(characters: [(String, String)]) -> String
{
var input: String = self
let count = characters.count
if count >= 1
{
for i in 1...count
{
let c = i - 1
let first = input
let working = first.replacingOccurrences(of: characters[c].0, with: characters[c].1)
input = working
}
}
return input
}
}
Try range(of with caseInsensitive and diacriticInsensitive options
let arr = dataSetArray.filter{ $0.localizedStandardRange(of: searchText) != nil }
without the extensions
You can use localizedStandardContains which returns a Boolean value indicating whether the string contains the given string, taking the current locale into account.
Declaration
func localizedStandardContains<T>(_ string: T) -> Bool where T : StringProtocol
Discussion
This is the most appropriate method for doing user-level string searches, similar to how searches are done generally in the system.
The search is locale-aware, case and diacritic insensitive. The exact
list of search options applied may change over time
extension Collection where Element: StringProtocol {
public func localizedStandardFilter(_ element: Element) -> [Element] {
filter { $0.localizedStandardContains(element) }
}
}
let array = ["cafe","Café Quente","CAFÉ","Coffe"]
let filtered = array.localizedStandardFilter("cafe")
filtered // ["cafe", "Café Quente", "CAFÉ"]
I have received this response from the server and I am sure there must be a more efficient way to convert it into an object.
I have the following response:
[
id=2997,rapidViewId=62,state=ACTIVE,name=Sprint7,startDate=2018-11-20T10:28:37.256Z,endDate=2018-11-30T10:28:00.000Z,completeDate=<null>,sequence=2992,goal=none
]
How do I convert it nicely into a well formed swift object in the simplest way?
Here is my attempt which gives me just the Sprint Value
if sprintJiraCustomField.count > 0 {
let stringOutput = sprintJiraCustomField.first?.stringValue // convert output to String
let name = stringOutput?.components(separatedBy: "name=") // get name section from string
let nameFieldRaw = name![1].components(separatedBy: ",") // split out to the comma
let nameValue = nameFieldRaw.first!
sprintDetail = nameValue// show name field
}
Not sure what format you want but the below code will produce an array of tuples (key, value) but all values are strings so I guess another conversion is needed afterwards
let items = stringOutput.components(separatedBy: ",").compactMap( {pair -> (String, String) in
let keyValue = pair.components(separatedBy: "=")
return (keyValue[0], keyValue[1])
})
This is a work for reduce:
let keyValueStrings = yourString.components(separatedBy: ",")
let dictionary = keyValueStrings.reduce([String: String]()) {
(var aggregate: [String: String], element: String) -> [String: String] in
let elements = element.componentsSeparatedByString("=")
let key = elements[0]
// replace nil with the value you want to use if there is no value
let value = (elements.count > 1) ? elements[1] : nil
aggregate[key] = value
return aggregate
}
This is a functional approach, but you can achieve the same using a for iteration.
So then you can use Swift’s basic way of mapping. for example you will have your custom object struct. First, you will add an init method to it. Then map your object like this:
init(with dictionary: [String: Any]?) {
guard let dictionary = dictionary else { return }
attribute = dictionary["attrName"] as? String
}
let customObjec = CustomStruct(dictionary: dictionary)
We already have some suggestion to first split the string at each comma and then split each part at the equals sign. This is rather easy to code and works well, but it is not very efficient as every character has to be checked multiple times. Writing a proper parser using Scanner is just as easy, but will run faster.
Basically the scanner can check if a given string is at the current position or give you the substring up to the next occurrence of a separator.
With that the algorithm would have the following steps:
Create scanner with the input string
Check for the opening bracket, otherwise fail
Scan up to the first =. This is the key
Consume the =
Scan up to the first , or ]. This is the value
Store the key/value pair
If there is a , consume it and continue with step 3
Consume the final ].
Sadly the Scanner API is not very Swift-friendly. With a small extension it is much easier to use:
extension Scanner {
func scanString(_ string: String) -> Bool {
return scanString(string, into: nil)
}
func scanUpTo(_ delimiter: String) -> String? {
var result: NSString? = nil
guard scanUpTo(delimiter, into: &result) else { return nil }
return result as String?
}
func scanUpTo(_ characters: CharacterSet) -> String? {
var result: NSString? = nil
guard scanUpToCharacters(from: characters, into: &result) else { return nil }
return result as String?
}
}
With this we can write the parse function like this:
func parse(_ list: String) -> [String: String]? {
let scanner = Scanner(string: list)
guard scanner.scanString("[") else { return nil }
var result: [String: String] = [:]
let endOfPair: CharacterSet = [",", "]"]
repeat {
guard
let key = scanner.scanUpTo("="),
scanner.scanString("="),
let value = scanner.scanUpTo(endOfPair)
else {
return nil
}
result[key] = value
} while scanner.scanString(",")
guard scanner.scanString("]") else { return nil }
return result
}
The following works in Playground:
func stringToInt(numberStr: String!) -> Int {
print(numberStr)
return Int(numberStr)!
}
let strNum1: String?
strNum1 = "1"
let result = stringToInt(numberStr: strNum1)
It returns 1 as expected.
In Xcode, a similar approach fails:
func stringToInt(numberStr: String!) -> Int {
print("\(numberStr!)")
let str = "\(numberStr!)"
print(Int(str))
return Int(str)!
}
The first print produces: Optional(1)
The second print produces: nil
The return statement fails because it is attempting to create an Int from a nil.
It must be something simple but I haven't been able to determine why it's not working. This is in Swift 3 and Xcode 8 BTW.
#Hamish:
In Xcode, I have a string with a numeric value. This:
print("number: (selectedAlertNumber) - unit: (selectedAlertUnit)")
...produces this:
number: Optional(1) - unit: Day
Then, I'm checking to see if either selectedAlertNumber of selecterAlertUnit != "-"
if selectedAlertNumber != "-" && selectedAlertUnit != "-" {
// set alert text
var unitStr = selectedAlertUnit
let alertNumber = stringToInt(numberStr: selectedAlertNumber)
if alertNumber > 1 {
unitStr.append("s")
}
let alertText = "...\(selectedAlertNumber) \(unitStr) before event."
alertTimeCell.setAlertText(alertText: alertText)
// set alert date/time
}
The let alertNumber = stringToInt... line is how I'm calling the function. I could just attempt the conversion there but I wanted to isolate the problem by wrapping the conversion in it's own function.
Using string interpolation to convert values to a String is usually not advised since the output may differ depending on optional status of the value. For example, consider these two functions:
func stringToInt(numberStr: String!) -> Int
{
print("\(numberStr!)")
let str = "\(numberStr!)"
return Int(str)!
}
func otherStringToInt(numberStr: String!) -> Int
{
print(numberStr)
let str = "\(numberStr)"
return Int(str)!
}
The only difference between these two is the ! in the second function when using string interpolation to get a String type value from numberStr. To be more specific, at the same line in function 1 compared to function 2, the string values are very different depending on whether or not the interpolated value is optional:
let str1: String = "1"
let str2: String! = "1"
let str3: String? = "1"
let otherStr1 = "\(str1)" // value: "1"
let otherStr2 = "\(str2)" // value: "Optional(1)"
let otherStr3 = "\(str2!)" // value: "1"
let otherStr4 = "\(str3)" // value: "Optional(1)"
let otherStr5 = "\(str3!)" // value: "1"
Passing otherStr2 or otherStr4 into the Int initializer will produce nil, since the string "Optional(1)" is not convertible to Int. Additionally, this will cause an error during the force unwrap. Instead of using string interpolation in your function, it would be better to just use the value directly since it's already a String.
func stringToInt(numberStr: String!) -> Int
{
return Int(numberStr)!
}
Let me know if this makes sense.
Also, my own personal feedback: watch out force unwrapping so frequently. In many cases, you're running the risk of getting an error while unwrapping a nil optional.
Using Swift, I'm trying to take a list of numbers input in a text view in an app and create a sum of this list by extracting each number for a grade calculator. Also the amount of values put in by the user changes each time. An example is shown below:
String of: 98,99,97,96...
Trying to get: 98+99+97+96...
Please Help!
Thanks
Use components(separatedBy:) to break up the comma-separated string.
Use trimmingCharacters(in:) to remove spaces before and after each element
Use Int() to convert each element into an integer.
Use compactMap (previously called flatMap) to remove any items that couldn't be converted to Int.
Use reduce to sum up the array of Int.
let input = " 98 ,99 , 97, 96 "
let values = input.components(separatedBy: ",").compactMap { Int($0.trimmingCharacters(in: .whitespaces)) }
let sum = values.reduce(0, +)
print(sum) // 390
For Swift 3 and Swift 4.
Simple way: Hard coded. Only useful if you know the exact amount of integers coming up, wanting to get calculated and printed/used further on.
let string98: String = "98"
let string99: String = "99"
let string100: String = "100"
let string101: String = "101"
let int98: Int = Int(string98)!
let int99: Int = Int(string99)!
let int100: Int = Int(string100)!
let int101: Int = Int(string101)!
// optional chaining (if or guard) instead of "!" recommended. therefore option b is better
let finalInt: Int = int98 + int99 + int100 + int101
print(finalInt) // prints Optional(398) (optional)
Fancy way as a function: Generic way. Here you can put as many strings in as you need in the end. You could, for example, gather all the strings first and then use the array to have them calculated.
func getCalculatedIntegerFrom(strings: [String]) -> Int {
var result = Int()
for element in strings {
guard let int = Int(element) else {
break // or return nil
// break instead of return, returns Integer of all
// the values it was able to turn into Integer
// so even if there is a String f.e. "123S", it would
// still return an Integer instead of nil
// if you want to use return, you have to set "-> Int?" as optional
}
result = result + int
}
return result
}
let arrayOfStrings = ["98", "99", "100", "101"]
let result = getCalculatedIntegerFrom(strings: arrayOfStrings)
print(result) // prints 398 (non-optional)
let myString = "556"
let myInt = Int(myString)
I want to convert the index of a letter contained within a string to an integer value. Attempted to read the header files but I cannot find the type for Index, although it appears to conform to protocol ForwardIndexType with methods (e.g. distanceTo).
var letters = "abcdefg"
let index = letters.characters.indexOf("c")!
// ERROR: Cannot invoke initializer for type 'Int' with an argument list of type '(String.CharacterView.Index)'
let intValue = Int(index) // I want the integer value of the index (e.g. 2)
Any help is appreciated.
edit/update:
Xcode 11 • Swift 5.1 or later
extension StringProtocol {
func distance(of element: Element) -> Int? { firstIndex(of: element)?.distance(in: self) }
func distance<S: StringProtocol>(of string: S) -> Int? { range(of: string)?.lowerBound.distance(in: self) }
}
extension Collection {
func distance(to index: Index) -> Int { distance(from: startIndex, to: index) }
}
extension String.Index {
func distance<S: StringProtocol>(in string: S) -> Int { string.distance(to: self) }
}
Playground testing
let letters = "abcdefg"
let char: Character = "c"
if let distance = letters.distance(of: char) {
print("character \(char) was found at position #\(distance)") // "character c was found at position #2\n"
} else {
print("character \(char) was not found")
}
let string = "cde"
if let distance = letters.distance(of: string) {
print("string \(string) was found at position #\(distance)") // "string cde was found at position #2\n"
} else {
print("string \(string) was not found")
}
Works for Xcode 13 and Swift 5
let myString = "Hello World"
if let i = myString.firstIndex(of: "o") {
let index: Int = myString.distance(from: myString.startIndex, to: i)
print(index) // Prints 4
}
The function func distance(from start: String.Index, to end: String.Index) -> String.IndexDistance returns an IndexDistance which is just a typealias for Int
Swift 4
var str = "abcdefg"
let index = str.index(of: "c")?.encodedOffset // Result: 2
Note: If String contains same multiple characters, it will just get the nearest one from left
var str = "abcdefgc"
let index = str.index(of: "c")?.encodedOffset // Result: 2
encodedOffset has deprecated from Swift 4.2.
Deprecation message:
encodedOffset has been deprecated as most common usage is incorrect. Use utf16Offset(in:) to achieve the same behavior.
So we can use utf16Offset(in:) like this:
var str = "abcdefgc"
let index = str.index(of: "c")?.utf16Offset(in: str) // Result: 2
When searching for index like this
⛔️ guard let index = (positions.firstIndex { position <= $0 }) else {
it is treated as Array.Index. You have to give compiler a clue you want an integer
✅ guard let index: Int = (positions.firstIndex { position <= $0 }) else {
Swift 5
You can do convert to array of characters and then use advanced(by:) to convert to integer.
let myString = "Hello World"
if let i = Array(myString).firstIndex(of: "o") {
let index: Int = i.advanced(by: 0)
print(index) // Prints 4
}
To perform string operation based on index , you can not do it with traditional index numeric approach. because swift.index is retrieved by the indices function and it is not in the Int type. Even though String is an array of characters, still we can't read element by index.
This is frustrating.
So ,to create new substring of every even character of string , check below code.
let mystr = "abcdefghijklmnopqrstuvwxyz"
let mystrArray = Array(mystr)
let strLength = mystrArray.count
var resultStrArray : [Character] = []
var i = 0
while i < strLength {
if i % 2 == 0 {
resultStrArray.append(mystrArray[i])
}
i += 1
}
let resultString = String(resultStrArray)
print(resultString)
Output : acegikmoqsuwy
Thanks In advance
Here is an extension that will let you access the bounds of a substring as Ints instead of String.Index values:
import Foundation
/// This extension is available at
/// https://gist.github.com/zackdotcomputer/9d83f4d48af7127cd0bea427b4d6d61b
extension StringProtocol {
/// Access the range of the search string as integer indices
/// in the rendered string.
/// - NOTE: This is "unsafe" because it may not return what you expect if
/// your string contains single symbols formed from multiple scalars.
/// - Returns: A `CountableRange<Int>` that will align with the Swift String.Index
/// from the result of the standard function range(of:).
func countableRange<SearchType: StringProtocol>(
of search: SearchType,
options: String.CompareOptions = [],
range: Range<String.Index>? = nil,
locale: Locale? = nil
) -> CountableRange<Int>? {
guard let trueRange = self.range(of: search, options: options, range: range, locale: locale) else {
return nil
}
let intStart = self.distance(from: startIndex, to: trueRange.lowerBound)
let intEnd = self.distance(from: trueRange.lowerBound, to: trueRange.upperBound) + intStart
return Range(uncheckedBounds: (lower: intStart, upper: intEnd))
}
}
Just be aware that this can lead to weirdness, which is why Apple has chosen to make it hard. (Though that's a debatable design decision - hiding a dangerous thing by just making it hard...)
You can read more in the String documentation from Apple, but the tldr is that it stems from the fact that these "indices" are actually implementation-specific. They represent the indices into the string after it has been rendered by the OS, and so can shift from OS-to-OS depending on what version of the Unicode spec is being used. This means that accessing values by index is no longer a constant-time operation, because the UTF spec has to be run over the data to determine the right place in the string. These indices will also not line up with the values generated by NSString, if you bridge to it, or with the indices into the underlying UTF scalars. Caveat developer.
In case you got an "index is out of bounds" error. You may try this approach. Working in Swift 5
extension String{
func countIndex(_ char:Character) -> Int{
var count = 0
var temp = self
for c in self{
if c == char {
//temp.remove(at: temp.index(temp.startIndex,offsetBy:count))
//temp.insert(".", at: temp.index(temp.startIndex,offsetBy: count))
return count
}
count += 1
}
return -1
}
}