return and exit from compactMap - swift

I am checking some dynamic texts with Regex Rules, using switch for each index of regex pattern array. everything is working fine but in the last case i want the map to stop mapping and do an early return! but seems like i can't return inside a map function! any better approach or solution ?
fileprivate func regexMatch(pattern regex: [String], in text: String) -> Dictionary<String, Any>
{
do
{
for (index, string) in regex.enumerated()
{
let regex = try NSRegularExpression(pattern: string, options: .caseInsensitive)
let results: [NSTextCheckingResult] = regex.matches(in: text, range: NSRange(text.startIndex..., in: text))
_ = results.compactMap
{
/* If i put a guard check to see if result is filled
* then return, works fine but iterates again for the next index and keeps returning till it’s over!
*/
switch index
{
case 0:
// Does something
case 1:
// Does something else
case 2:
// Does the last thing
// If succeed finding the match! Just return or exit the map!
let carNames = String(Range($0.range, in: text).map { String(text[$0]) }!).lowercased()
for case let car as Car in carsList!
{
if spottedCarNamesInReceipt.contains(car.name!.lowercased())
{
result["car"] = car
return // This does work though, but the map starts iterating again over the next index!
}
}
default:
break;
}
}
}
return result
} catch let error
{
print("Invalid regex: \(error.localizedDescription)")
return [:]
}

You don't need to use compactMap if you don't use results. And there is no way to exit from compactMap. Use for in cycle.

Using return statement forEach loop or any kind of Maps, exits only for the current call in the closure while for...in loop exits all the next subsequent calls as well. so for...in solves the problem if an early exit is needed.

Related

What is the purpose of .enumerated() and .map() in this code?

I'm working a tutorial from https://www.raywenderlich.com/921-cocoa-bindings-on-macos. I'm wondering what the .enumerated() and .map() functions are operating on in this section:
#IBAction func searchClicked(_ sender: Any) {
guard let resultsNumber = Int(numberResultsComboBox.stringValue)
else {
return
}
iTunesRequestManager.getSearchResults(searchTextField.stringValue, results: resultsNumber, langString: "en_us") { (results, error) in
let itunesResults = results.map {
return Result(dictionary: $0)
}
.enumerated()
.map({ (index, element) -> Result in
element.rank = index + 1
return element
})
DispatchQueue.main.async {
self.searchResultsController.content = itunesResults
print(self.searchResultsController.content!)
}
}
}
I can usually figure out most things eventually in Swift but I'm stumped here and the explanatory text isn't clear to me either. I hope someone can help me understand this part of the tutorial. Thanks!
Map is used for modifications. At this point you are basically initialising an object of Result by giving results array as a param to it:
results.map {
return Result(dictionary: $0)
}
$0 means the first input. In a following case, $0 is equal to param(we just gave it a name):
results.map { param in
return Result(dictionary: param)
}
.enumerated() returns each element of an array with its index number. Without it you would have only the element like this:
.map({ (element) -> Result in
// you don't have `index` value inside the closure anymore
// element.rank = index + 1
return element
})
Note that the element in the closure above is the same Result(dictionary: $0) object that you created in a previous map function.
At the end, you are making and modification by assigning elements index number increased by 1 to the element's rank property, and returning it:
.map({ (index, element) -> Result in
// element.rank = index + 1
return element
})
Note that the value we get after the 3rd step, including all modification is assigned to let itunesResults.

How to merge String & Int to get in ONE flatMap

enum Input { case text(String); case page(Int) }
I am managing pagination with keyword search to API method.
Now I either can pass keywords or page number, but not both at same time in Rx.
I have written following code with help of some existing available gist
let start = Observable.merge(reload, loadNext)
let stringObservable = keyword.asObservable().map { Input.text($0) }
let intObservable = start.asObservable().map { Input.page($0) }
let request_call = Observable.of(stringObservable, intObservable).merge()
let page = request_call
.flatMap { input in
Observable.combineLatest(Observable.just($0), api.loadData(page: $0, keyword: "breaking")) { (pageNumber: $0, items: $1) }
.materialize()
.filter { $0.isCompleted == false }
}
.share()
start keep Page Number, & keyword keeps search keywords.
I need to merge both, I did using ENUM & Merge,
Now I have to call API, but showing as Input,
So How can I get both values in one flatMap
Get rid of the Input enum and use combineLatest instead of merge.
Then request_call will be an Observable<(String, Int)> and you can use the two values in the loadData function.
let start = Observable.merge(reload, loadNext)
let stringObservable = keyword.asObservable()
let intObservable = start.asObservable()
let request_call = Observable.combineLatest(stringObservable, intObservable)
let page = request_call
.flatMap { text, page in
Observable.combineLatest(Observable.just(page), api.loadData(page: page, keyword: text)) { (pageNumber: $0, items: $1) }
.materialize()
.filter { $0.isCompleted == false }
}
.share()

What are some ways I can optimize this special cased closure?

I want to use the sorted method on an array.
public func sorted(by areInIncreasingOrder: (Self.Iterator.Element, Self.Iterator.Element) -> Bool) -> [Self.Iterator.Element]
And I can see that it takes a function (which takes 2 Elements and returns a Bool), and then returns an an array of the Element (sorted)
Where I am getting stuck, is by trying to pass an anonymous function (closure), using $0 and $1 as arguments
I want to add a special case, for a specific key ("module")
And then I want to access a property on $0 and $1
But this means, ostensibly, that I have to cast those arguments, so that I can then access the properties on those arguments
And to make things worse, because the arguments could either be String or Node, I currently have repeated code (the switch statement)
So anyways, do any of you Swift veterans see things that I could do to make this better?
return self.sorted(by: {
for descriptor in sortDescriptors {
if descriptor.key == "module" {
if let firstNode = $0 as? Node {
if let secondNode = $1 as? Node {
let firstModule = firstNode.module?.name ?? ""
let secondModule = secondNode.module?.name ?? ""
let newDescriptor = NSSortDescriptor(key: nil, ascending: descriptor.ascending, selector: descriptor.selector)
switch newDescriptor.compare(firstModule, to: secondModule) {
case .orderedAscending:
return true
case .orderedDescending:
return false
case .orderedSame:
continue
}
}
}
}
switch descriptor.compare($0, to: $1) {
case .orderedAscending:
return true
case .orderedDescending:
return false
case .orderedSame:
continue
}
}
return false
})
}

How to I get the first or last few characters of a string in a method chain?

If I use functional-style method chains for string manipulation, I can not use the usual machinery for getting the first or last few characters: I do not have access to a reference to the current string, so I can not compute indices.
Example:
[some, nasty, objects]
.map( { $0.asHex } )
.joined()
.<first 100>
.uppercased()
+ "..."
for a truncated debug output.
So how to I implement <first 100>, or do I have to break the chain?
I don't know of any API that does this. Fortunately, writing our own is an easy exercise:
extension String {
func taking(first: Int) -> String {
if first <= 0 {
return ""
} else if let to = self.index(self.startIndex,
offsetBy: first,
limitedBy: self.endIndex) {
return self.substring(to: to)
} else {
return self
}
}
}
Taking from the end is similar.
Find full code (including variants) and tests here.

Condition after variable binding in guard swift compilation issue

I am using the nice guard statement from Swift 3.0 (in Xcode 8.0) and have the following function:
func parse(suffix s: String) throws -> Instruction {
guard let count = Int(s) where count >= 0 else {
throw InstructionParsingError(message: s + " should be a positive integer")
}
return CallInstruction(argCount: count)
}
My issue is that the swift compiler complains twice about the line containing my guard statement:
CallInstruction.swift:42:29: Boolean condition requires 'where' to separate it from variable binding
CallInstruction.swift:42:30: Expected ',' joining parts of a multi-clause condition
I tried
replacing the where with a , then the second error disappears but the first one is still there.
replacing the where with , where but then this line can't even be parsed
replacing the count in the where by Int(s) but have the same errors.
How should I change my code so that it compiles? (With a nice single guard statement I mean, of course I could have multiple guards, or ifs or switch but from what I read about the guard statement I should be able to have a clean readable line).
For solving of this issue i recommend you to use model Swift syntax in guard statement that replace where with ,.
func parse(suffix s: String) {
guard let count = Int(s), count >= 0 else {
return
}
}
Also it is possible to use if let statement :
func parseTwo(suffix s: String) {
if let count = Int(s), count >= 0 {
// done
print(count)
} else {
return
}
}