How to get the Lowest and Highest Value from Dictionary in Swift - ios

I am facing a problem in getting the values to make the desired combination. I am using a filter screen in my app. I asked this question to get the first and last element from the question How to put the of first and last element of Array in Swift and it is working but the problem is in my FiterVC first I selected the option $400 - $600 and then I selected the $200 - $400. After selecting I am getting these values in my currentFilter variable.
private let menus = [
["title": "Price", "isMultiSelection": true, "values": [
["title": "$00.00 - $200.00"],
["title": "$200.00 - $400.00"],
["title": "$400.00 - $600.00"],
["title": "$600.00 - $800.00"],
["title": "$800.00 - $1000.00"],
]],
["title": "Product Rating", "isMultiSelection": true, "values": [
["title": "5"],
["title": "4"],
["title": "3"],
["title": "2"],
["title": "1"]
]],
["title": "Arriving", "isMultiSelection": true, "values": [
["title": "New Arrivials"],
["title": "Coming Soon"]
]]
]
private var currentFilters = [String:Any]()
Selecting values in didSelect method:-
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
if tableView === self.menuTableView {
self.currentSelectedMenu = indexPath.row
self.menuTableView.reloadData()
self.valueTableView.reloadData()
}
else {
if let title = self.menus[self.currentSelectedMenu]["title"] as? String, let values = self.menus[self.currentSelectedMenu]["values"] as? [[String:Any]], let obj = values[indexPath.row]["title"] as? String {
if let old = self.selectedFilters[title] as? [String], let isAllowedMulti = self.menus[self.currentSelectedMenu]["isMultiSelection"] as? Bool, !old.isEmpty, !isAllowedMulti {
var temp = old
if old.contains(obj), let index = old.index(of: obj) {
temp.remove(at: index)
}
else {
temp.append(obj)
}
self.selectedFilters[title] = temp
}
else {
self.selectedFilters[title] = [obj]
}
self.valueTableView.reloadData()
}
}
}
And on Apply button click:-
#IBAction func applyButtonAction(_ sender: UIButton) {
self.delegate?.didSelectedFilters(self, with: self.selectedFilters)
printD(self.selectedFilters)
self.dismiss(animated: true, completion: nil)
}
When I am printing the selectedFilters I am getting these values:-
currentFilters ["Price": ["$400.00 - $600.00", "$200.00 - $400.00"]]
And by using this method I am getting the first and last value from dictionary like this:-
if let obj = currentFilters["Price"] as? [String] {
self.priceRange = obj
printD(self.priceRange)
let first = priceRange.first!.split(separator: "-").first!
let last = priceRange.last!.split(separator: "-").last!
let str = "\(first)-\(last)"
let str2 = str.replacingOccurrences(of: "$", with: "", options: NSString.CompareOptions.literal, range: nil)
newPrice = str2
printD(newPrice)
}
The result is :-
400.00 - 400.00
but what I actually want is 200 - 600. How can I do this. Please help?

However there are many solutions available to this problem but simplest and most relevant solution is highlighted here:
let min = 1000, max = 0
if let obj = currentFilters["Price"] as? [String] {
self.priceRange = obj
printD(self.priceRange)
for str in obj{
let first = str.split(separator: "-").first!.replacingOccurrences(of: "$", with: "", options:
NSString.CompareOptions.literal, range: nil)
let last = str.split(separator: "-").last!.replacingOccurrences(of: "$", with: "", options:
NSString.CompareOptions.literal, range: nil)
if Int(first) < min{
min = first
}
if Int(last) > max{
max = last
}
}
let str = "\(min)-\(max)"
newPrice = str
printD(newPrice)
}

You can use an enum with functions to load your prices ranges instead of using directly Strings.
enum PriceRange: String {
case
low = "200 - 400",
high = "400 - 600"
static let priceRanges = [low, high]
func getStringValue() -> String {
return self.rawValue
}
func getMinValueForRange(stringRange: String) -> Int? {
switch stringRange {
case "200 - 400":
return 200;
case "400 - 600":
return 400;
default:
return nil
}
}
func getMaxValueForRange(stringRange: String) -> Int? {
switch stringRange {
case "200 - 400":
return 400;
case "400 - 600":
return 600;
default:
return nil
}
}
}
Then you can use/add functions to get the result that you are looking for.

Here's the small solution of your problem. It all breaks down to that: you get all the values from your filters, and then iterating over getting all the values from it. That way you avoid comparing the pairs and instead comparing the exact values.
let currentFilters = ["Price": ["$400.00 - $600.00", "$200.00 - $400.00"]]
let ranges = currentFilters["Price"]!
var values: [Double] = []
ranges.forEach { range in // iterate over each range
let rangeValues = range.split(separator: "-")
for value in rangeValues { // iterate over each value in range
values.append(Double( // cast string value to Double
String(value)
.replacingOccurrences(of: " ", with: "")
.replacingOccurrences(of: "$", with: "")
)!)
}
}
let min = values.min() // prints 200
let max = values.max() // prints 600

We can do by steps -
1 - Get the price ranges from the dictionary
2 - Iterate over those price ranges
3 - Split the range by "-" and check if the prices count is 2 else this is an invalid price range
4 - Get the numeric components from the two prices and then compare from the previously saved min and max values and update them accordingly
Try this -
if let priceRanges = currentFilters["Price"] as? [String] { // Extract the price ranges from dictionary
var minValue: Double? // Holds the min value
var maxValue: Double? // Holds the max value
for priceRange in priceRanges { Iterate over the price ranges
let prices = priceRange.split(separator: "-") // Separate price range by "-"
guard prices.count == 2 else { // Checks if there are 2 prices else price range is invalid
print("invalid price range")
continue // skip this price range when invalid
}
let firstPrice = String(prices[0]).numericString // Extract numerics from the first price
let secondPrice = String(prices[1]).numericString // Same for the second price
if let value = Double(firstPrice) { // Check if the price is valid amount by casting it to double
if let mValue = minValue { // Check if we have earlier saved a minValue from a price range
minValue = min(mValue, value) // Assign minimum of current price and earlier save min price
} else {
minValue = value // Else just save this price to minValue
}
}
if let value = Double(secondPrice) { // Check if the price is valid amount by casting it to double
if let mValue = maxValue { // Check if we have earlier saved a maxValue from a price range
maxValue = max(mValue, value) // Assign maximum of current price and earlier save max price
} else {
maxValue = value // Else just save this price to maxValue
}
}
}
if let minV = minValue, let maxV = maxValue { // Check if we have a min and max value from the price ranges
print("\(minV) - \(maxV)")
} else {
print("unable to find desired price range") // Else print this error message
}
}
extension String {
/// Returns a string with all non-numeric characters removed
public var numericString: String {
let characterSet = CharacterSet(charactersIn: "01234567890.").inverted
return components(separatedBy: characterSet)
.joined()
}
}

Related

How to put the of first and last element of Array in Swift

I have a dictionary like this
["Price": ["$00.00 - $200.00", "$200.00 - $400.00", "$600.00 - $800.00"]]
Now I am storing all the dictionary value in array like this
var priceRange: [String] = [String]()
if let obj = currentFilters["Price"] as? [String] {
self.priceRange = obj
printD(self.priceRange)
}
And by the use of Array.first and Array.last method I will get the values of first element and last element of my array.
let first = priceRange.first ?? "" // will get("[$00.00 - $200.00]")
let last = priceRange.last ?? "" // will get("[$600.00 - $800.00]")
But What I actually want is I want the $00.00 from first and $800 from last to make the desired combination of [$00.00 - $800.00].
How can I do this. Please help?
You need to take first value ("$00.00 - $200.00"), then last value ("$600.00 - $800.00"), then split them by "-" symbol and take first and last values respectively and combine it to single string.
let currentFilters = ["Price": ["$00.00 - $200.00", "$200.00 - $400.00", "$600.00 - $800.00"]]
var priceRange: [String] = [String]()
if let obj = currentFilters["Price"] as? [String] {
priceRange = obj
print(priceRange)
}
let first = priceRange.first!.split(separator: "-").first!
let last = priceRange.last!.split(separator: "-").last!
let range = "\(first) - \(last)"
For better optionals handling you can use this (NB, I'm following my over-descriptive coding style. This code can be much more compact)
func totalRange(filters: [String]?) -> String? {
guard let filters = filters else { return nil }
guard filters.isEmpty == false else { return nil }
guard let startComponents = priceRange.first?.split(separator: "-"), startComponents.count == 2 else {
fatalError("Unexpected Filter format for first filter") // or `return nil`
}
guard let endComponents = priceRange.last?.split(separator: "-"), endComponents.count == 2 else {
fatalError("Unexpected Filter format for last filter") // or `return nil`
}
return "\(startComponents.first!) - \(endComponents.last!)"
}
let range = totalRange(filters: currentFilters["Price"])
let range1 = totalRange(filters: currentFilters["Not Exists"])
Past the code above to the playground. It can be written much shorter way, but I kept it like that for the sake of descriptivity
You can do the following:
let r = ["$00.00 - $200.00", "$200.00 - $400.00", "$600.00 - $800.00"]
.reduce("", +) //Combine all strings
.components(separatedBy: " - ") //Split the result
.map({ String($0) }) //Convert from SubString to String
print(r.first) //prints $00.00
print(r.last) // prints $800.00
let newRange = [r.first!, r.last!].joined(separator: " - ")
func priceRange(with priceRanges: [String]) -> String? {
guard
let min = priceRanges.first?.components(separatedBy: " - ").first,
let max = priceRanges.last?.components(separatedBy: " - ").last else { return nil }
return "[\(min) - \(max)]"
}
print(priceRange(
with: [
"$00.00 - $200.00",
"$200.00 - $400.00",
"$600.00 - $800.00"
]
))
You can use split to split up the range based on the - character than remove the whitespaces.
let first = priceRange.first?.split(separator: "-").first?.trimmingCharacters(in: .whitespaces) ?? ""
let last = priceRange.last?.split(separator: "-").last?.trimmingCharacters(in: .whitespaces) ?? ""
Use this You can find your values:
if let obj = priceRange as? [String] {
let max = priceRange.max()
let min = priceRange.min()
print(max?.components(separatedBy: " - ").map({$0.trimmingCharacters(in: .whitespacesAndNewlines)}).max()) //800
print(min?.components(separatedBy: " - ").map({$0.trimmingCharacters(in: .whitespacesAndNewlines)}).min()) //00
}
let amountsArray = ["$00.00 - $200.00", "$200.00 - $400.00", "$600.00 - $800.00"]
let amounts = amountsArray.reduce("") { $0 + $1 }.split(separator: "-")
if let first = amounts.first, let last = amounts.last {
print("[\(first)-\(last)]")
}
//use components(separatedBy:) :
let prices = f.components(separatedBy: " - ")
prices.first // $600.00
prices.last // $800.00
You could also use String.components(separatedBy:):
var text = "100$ - 200$"
var new = text.components(separatedBy: " - ")[0]
print(new) // Prints 100$

Swift code to produce a number of possible anagrams from a selected word

I've attempted to research ways to take a given word and calculate the number of possible anagrams a user can make from that word eg an 8 letter word such as snowbanks has 5 eight letter possibilities, 25 seven letter possibilities, etc (those are made up numbers). My initial plan would be to iterate over a dictionary list and check each of the words to see if it is an anagram of the word in question as I've seen suggested in other places.
Rearrange Letters from Array and check if arrangement is in array
seemed very promising, except that it is in objective C and when I tried to convert it to Swift using Swiftify I couldn't get it to work as shown below:
func findAnagrams() -> Set<AnyHashable>? {
let nineCharacters = [unichar](repeating: 0, count: 8)
let anagramKey = self.anagramKey()
// make sure this word is not too long/short.
if anagramKey == nil {
return nil
}
(anagramKey as NSString?)?.getCharacters(nineCharacters, range: NSRange)
let middleCharPos = Int((anagramKey as NSString?)?.range(of: (self as NSString).substring(with: NSRange)).location ?? 0)
var anagrams = Set<AnyHashable>()
// 0x1ff means first 9 bits set: one for each character
for i in 0...0x1ff {
// skip permutations that do not contain the middle letter
if (i & (1 << middleCharPos)) == 0 {
continue
}
var length: Int = 0
var permutation = [unichar](repeating: 0, count: 9)
for bit in 0...9 {
if true {
permutation[length] = nineCharacters[bit]
length += 1
}
}
if length < 4 {
continue
}
let permutationString = String(permutation)
let matchingAnagrams = String.anagramMap()[permutationString] as? [Any]
for word: String? in matchingAnagrams {
anagrams.insert(word)
}
}
return anagrams
}
class func anagramMap() -> [AnyHashable: Any]? {
var anagramMap: [AnyHashable: Any]
if anagramMap != nil {
return anagramMap
}
// this file is present on Mac OS and other unix variants
let allWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
var map = [AnyHashable: Any]()
autoreleasepool {
allWords?.enumerateLines(invoking: {(_ word: String?, _ stop: UnsafeMutablePointer<ObjCBool>?) -> Void in
let key = word?.anagramKey()
if key == nil {
return
}
var keyWords = map[key] as? [AnyHashable]
if keyWords == nil {
keyWords = [AnyHashable]()
map[key] = keyWords
}
if let aWord = word {
keyWords?.append(aWord)
}
})
}
anagramMap = map
return anagramMap
}
func anagramKey() -> String? {
let lowercaseWord = word.lowercased()
// make sure to take the length *after* lowercase. it might change!
let length: Int = lowercaseWord.count
// in this case we're only interested in anagrams 4 - 9 characters long
if length < 3 || length > 9 {
return nil
}
let sortedWord = [unichar](repeating: 0, count: length)
(lowercaseWord as NSString).getCharacters(sortedWord, range: NSRange)
qsort_b(sortedWord, length, MemoryLayout<unichar>.size, {(_ aPtr: UnsafeRawPointer?, _ bPtr: UnsafeRawPointer?) -> Int in
let a = Int(unichar(aPtr))
let b = Int(unichar(bPtr))
return b - a
})
return String(describing: sortedWord)
}
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSMakeRange(0, word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
}
I've also tried the following in an attempt to just produce a list of words that I could iterate over to check for anagrams (I have working code that checks guesses vs the main word to check for anagrams) but I wasn't able to get them to work, possibly because they require a file to be copied to the app, since I was under the impression that the phone has a dictionary preloaded that I could use for words (although I may be mistaken):
var allTheWords = try? String(contentsOfFile: "/usr/share/dict/words", encoding: .utf8)
for line: String? in allTheWords?.components(separatedBy: "\n") ?? [String?]() {
print("\(line ?? "")")
print("Double Fail \(allTheWords)")
}
and
if let wordsFilePath = Bundle.main.path(forResource: "dict", ofType: nil) {
do {
let wordsString = try String(contentsOfFile: wordsFilePath)
let wordLines = wordsString.components(separatedBy: NSCharacterSet.newlines)
let randomLine = wordLines[Int(arc4random_uniform(UInt32(wordLines.count)))]
print(randomLine)
} catch { // contentsOfFile throws an error
print("Error: \(error)")
}
}
}
I looked at UIReferenceLibraryViewController as well in an attempt to use it to produce a list of words instead of defining a selected word, but the following isn't a valid option.
let words = UIReferenceLibraryViewController.enumerated
Any assistance would be greatly appreciated!

How to know data coming from JSON is a Float or an Integer in Swift 3?

I am getting data from Json and displaying it in table view how to check whether the number is float or double or integer in swift 3 if it is float how to get the no.of digits after decimal can anyone help me how to implement this in swift 3 ?
if specialLoop.attributeCode == "special_price" {
let attributeString: NSMutableAttributedString = NSMutableAttributedString(string: "$ \((arr.price))")
attributeString.addAttribute(NSStrikethroughStyleAttributeName, value: 1, range: NSMakeRange(0, attributeString.length))
let specialPrice = specialLoop.value.replacingOccurrences(of: ".0000", with: "0")
print(specialPrice)
cell.productPrice.text = "$ \(specialPrice)"
cell.specialPriceLabel.isHidden = false
cell.specialPriceLabel.attributedText = attributeString
break
}
else {
cell.specialPriceLabel.isHidden = true
let price = arr.price
print(price)
cell.productPrice.text = "$ \( (price))0"
}
You can use (if let)
let data = [String: Any]()
if let value = data["key"] as? Int {
} else if let value = data["key"] as? Float {
} else if let value = data["key"] as? Double {
}
as describe below, you can find a type of any object (whether custom class or built-in class like - String, Int, etc.).
class demo {
let a: String = ""
}
let demoObj = demo()
print(type(of: demoObj))
--> Output: "demo.Type"

How to trim a String using Swift 3

My code snippet is:
unwanted = " £€₹jetztabfromnow"
let favouritesPriceLabel = priceDropsCollectionView.cells.element(boundBy: UInt(index)).staticTexts[IPCUIAHighlightsPriceDropsCollectionViewCellPriceLabel].label
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: "jetzt").flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
favouritesHighlightsDictionary[favouritesTitleLabel] = favouritesPriceLabelTrimmed
My problem is, this didn't work:
let favouritesPriceLabelTrimmed = favouritesPriceLabel.components(separatedBy: unwanted).flatMap { String($0.trimmingCharacters(in: .whitespaces)) }.last
I have a price like "from 3,95 €" - I want to cut all currencies "£€₹" and words like "from" or "ab"
Do you have a solution for me, what I can use here?
Rather than mess around with trying to replace or remove the right characters or using regular expressions, I'd go with Foundation's built-in linguistic tagging support. It will do a lexical analysis of the string and return tokens of various types. Use it on this kind of string and it should reliably find any numbers in the string.
Something like:
var str = "from 3,95 €"
let range = Range(uncheckedBounds: (lower: str.startIndex, upper: str.endIndex))
var tokenRanges = [Range<String.Index>]()
let scheme = NSLinguisticTagSchemeLexicalClass
let option = NSLinguisticTagger.Options()
let tags = str.linguisticTags(in: range, scheme: scheme, options: option, orthography: nil, tokenRanges: &tokenRanges)
let tokens = tokenRanges.map { str.substring(with:$0) }
if let numberTagIndex = tags.index(where: { $0 == "Number" }) {
let number = tokens[numberTagIndex]
print("Found number: \(number)")
}
In this example the code prints "3,95". If you change str to "from £28.50", it prints "28.50".
One way is to place the unwanted strings into an array, and use String's replacingOccurrences(of:with:) method.
let stringToScan = "£28.50"
let toBeRemoved = ["£", "€", "₹", "ab", "from"]
var result = stringToScan
toBeRemoved.forEach { result = result.replacingOccurrences(of: $0, with: "") }
print(result)
...yields "28.50".
If you just want to extract the numeric value use regular expression, it considers comma or dot decimal separators.
let string = "from 3,95 €"
let pattern = "\\d+[.,]\\d+"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
print(string.substring(with: start..<end)) // 3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}
I asked if you had a fixed locale for this string, because then you can use the locale to determine what the decimal separator is: For example, try this in a storyboard.
let string = "some initial text 3,95 €" // define the string to scan
// Add a convenience extension to Scanner so you don't have to deal with pointers directly.
extension Scanner {
func scanDouble() -> Double? {
var value = Double(0)
guard scanDouble(&value) else { return nil }
return value
}
// Convenience method to advance the location of the scanner up to the first digit. Returning the scanner itself or nil, which allows for optional chaining
func scanUpToNumber() -> Scanner? {
var value: NSString?
guard scanUpToCharacters(from: CharacterSet.decimalDigits, into: &value) else { return nil }
return self
}
}
let scanner = Scanner(string: string)
scanner.locale = Locale(identifier: "fr_FR")
let double = scanner.scanUpToNumber()?.scanDouble() // -> double = 3.95 (note the type is Double?)
Scanners are a lot easier to use than NSRegularExpressions in these cases.
You can filter by special character by removing alphanumerics.
extension String {
func removeCharacters(from forbiddenChars: CharacterSet) -> String {
let passed = self.unicodeScalars.filter { !forbiddenChars.contains($0) }
return String(String.UnicodeScalarView(passed))
}
}
let str = "£€₹jetztabfromnow12"
let t1 = str.removeCharacters(from: CharacterSet.alphanumerics)
print(t1) // will print: £€₹
let t2 = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(t2) // will print: 12
Updated 1:
var str = "£3,95SS"
str = str.replacingOccurrences(of: ",", with: "")
let digit = str.removeCharacters(from: CharacterSet.decimalDigits.inverted)
print(digit) // will print: 395
let currency = str.removeCharacters(from: CharacterSet.alphanumerics)
print(currency) // will print: £
let amount = currency + digit
print(amount) // will print: £3,95
Update 2:
let string = "£3,95SS"
let pattern = "-?\\d+(,\\d+)*?\\.?\\d+?"
do {
let regex = try NSRegularExpression(pattern: pattern, options: [])
if let match = regex.firstMatch(in: string, range: NSRange(location: 0, length: string.utf16.count)) {
let range = match.range
let start = string.index(string.startIndex, offsetBy: range.location)
let end = string.index(start, offsetBy: range.length)
let digit = string.substring(with: start..<end)
print(digit) //3,95
let symbol = string.removeCharacters(from: CharacterSet.symbols.inverted)
print(symbol) // £
print(symbol + digit) //£3,95
} else {
print("Not found")
}
} catch {
print("Regex Error:", error)
}

Finding a value in an array of arrays (similar to VLOOKUP function in Excel) in Swift

I am quite new to Swift 3 and to programming languages in general. I have the following arrays inside an array and a variable income:
let testArray: [[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[4000,22.5],
]
var income: Double = 3500
What I want to do is something similar to the VLOOKUP function in Excel: I want to find in the first column of the arrays (i.e. 0, 1000, 3000, 4000) a number which is equal or immediately smaller than my variable. In this case, as income = 3500, the program should return 3000. I tried using filter() but I don't know how to work with the arrays inside the array. Any help appreciated.
You can proceed as follows.
Get the first column of the array:
let firstColumn = testArray.map { $0[0] }
print(firstColumn) // [0.0, 1000.0, 3000.0, 4000.0]
Restrict to those elements which are less than or equal to the
given amount:
let filtered = firstColumn.filter { $0 <= income }
print(filtered) // [0.0, 1000.0, 3000.0]
Get the maximal element of the filtered array. If the elements are
sorted in increasing order then you can use last instead of max():
let result = filtered.max()!
// Or: let result = filtered.last!
print(result) // 3000.0
Putting it all together:
let result = testArray.map { $0[0] }.filter { $0 <= income }.max()!
print(result) // 3000.0
A possible optimization is to combine map and filter into
flatMap:
let result = testArray.flatMap { $0[0] <= income ? $0[0] : nil }.max()!
print(result) // 3000.0
This code assumes that there is at least one matching element,
otherwise last! or max()! would crash. If that is not guaranteed:
if let result = testArray.flatMap( { $0[0] <= income ? $0[0] : nil }).max() {
print(result) // 3000.0
} else {
print("no result")
}
Or provide a default value (0.0 in this example):
let result = testArray.flatMap( { $0[0] <= income ? $0[0] : nil }).max() ?? 0.0
print(result) // 3000.0
Something like this:
let testArray: [[Double]] = [
[0,0],
[1000,20.5],
[3000,21],
[3500,22.5],
[3300,21],
]
let income: Double = 3500
var closest = testArray[0][0]
var closestDif = closest - income
for innerArray in testArray {
let value = innerArray[0]
let thisDif = value - income
guard thisDif <= 0 else {
continue
}
if closestDif < thisDif {
closestDif = thisDif
closest = value
guard closestDif != 0 else {
break
}
}
}
print(closest)

Resources