Using If-Let and checking the output in a single line - ios

This is just an example to illustrate what I am trying to achieve.
I want to check if an optional contains a value and if it is greater than 0. I currently have it this way:
if let value = Double(textFieldText) {
if value > 0 {
return true
}
}
Is there any way to achieve this in a single line? Something like:
if let value = Double(textFieldText) && value > 0{
return true
}

You can use where clause:
if let value = Double(textFieldText) where value > 0 {
Another option using nil coalescing operator:
if Double(textFieldText) ?? -Double.infinity > 0 {
Thanks to comments below which help me realize nil > 0 doesn't throw an error:
if Double(textFieldText) > 0 {
is by far the simplest option.

Solution in 1 line
I think the simplest way to write your logic is this
return Double(textFieldText) > 0
Test
func foo(textFieldText:String) -> Bool {
return Double(textFieldText) > 0
}
foo("1") // true
foo("-1") // false
foo("a") // false
foo("0") // false
foo("123a") // false
Why does this work?
When 2 values are compared in Swift and (exactly) one of them is nil, then nil is always less than the other value.
So every time the initialiser of Double does fail, like here
return Double("a") > 0
the expression becomes
return nil > 0
which is false.
The only way the expression does return true is when the input string is parsed as a Double value which is actually greater then 0.
Which is exactly the logic you where looking for :)

With a where clause:
if let value = Double(textFieldText) where value > 0 {
return true
}
Or simply:
if Double(textFieldText) > 0 {
return true
}

One more way you can do the same thing and that would steer you away from ugly if let nesting.
func hello(values:String) -> Bool {
guard let value = Double(values) where value > 0 else {
return false
}
return true
}

Try this.
if let value = textFieldText as? Double where value > 0 {
return true
}

Related

Swift sort array by Bool value and TimeStamp

I have an array of messages that I want to sort by whether they are unread or not, and their time stamp. These messages will be displayed in a TableView.
All unread messages should be displayed at the top, and then sorted by their timestamp.
Below is what I have right now; but what seems to be happening is that the items are displayed only by time stamp, without sorting them by unread at the top and then unread messages after.
let channelsSortedByUnreadFirst = dataStore.messages.sorted { $0.isUnread == true && $1.isUnread == false }
let timeSortedItems = channelsSortedByUnreadFirst.sorted(by: { $0.timeStamp > $1.timeStamp })
messagesTableViewSection.items = timeSortedItems
How do I sort these messages by both isUnread and timeStamp?
Bool does not conform to Comparable. What you need is to extend Bool type and convert its value to a type that conforms to Comparable like integer 0 or 1 and sort it using its value:
extension Bool {
var value: Int { self ? 1 : 0 }
}
true.value // 1
false.value // 0
Now you can use the boolean value when sorting your collection:
let channelsSorted = dataStore.messages.sorted { ($0.isUnread.value, $0.timeStamp) > ($1.isUnread.value, $1.timeStamp) }
Another way using Swift Algorithms stablePartition(by:):
import Algorithms
var messages = dataStore.messages
messages.sort { $0.timeStamp > $1.timeStamp }
let firstReadIndex = messages.stablePartition { !$0.isUnread }
Another Way that might be more intuitive but uses multiple passes.
print(messages.filter(\.isUnread).sorted { $0.timeStamp > $1.timeStamp } + messages.filter { !$0.isUnread })

Swift: Combine condition and if-let with logical or

I am trying to do something that would logically look like this:
if text == "" || let i = Int(text) where i < 2 {
// do something; don't care about the value of i
}
Of course this isn't a valid condition -- what would the value of i be if text == "" is the part that holds? But, since I'm only interested in the value of i inside the where clause, I was hoping there is a nice way of achieving the same effect, namely executing the same closure if either condition holds. My current solution is to extract the closure and call it from two separate if blocks, but that's pretty hairy-looking.
The equivalent to your code example would be:
if text == "" || Int(text) ?? 2 < 2 {
print("valid")
// do your previous "something
} else {
print("invalid")
}
which yields
"" -> valid
"1" -> valid
"2" -> invalid
"abc" -> invalid
If you're doing this kind of comparison regularly, you could create your own operator in order to compare an optional with a given closure representing your condition for success. If the unwrapped value meets the condition, it'll return true – else false.
For example:
infix operator ?& {precedence 130 }
func ?&<T>(lhs: T?, #noescape rhs:(T)->Bool) -> Bool {
return lhs != nil ? rhs(lhs!) : false
}
...
if text == "" || Int(text) ?& {$0 < 2} {
print("valid")
} else {
print("invalid")
}
You could also overload the existing < operator to do this, but this may impact already existing code that relies on nil being less than a non-optional value.
func <<T:Comparable>(lhs: T?, rhs:T) -> Bool {
return lhs != nil ? (lhs! < rhs) : false
}
...
if text == "" || Int(text) < 2 {
print("valid")
} else {
print("invalid")
}
Perhaps a more "Swifty" way to handle optional values is with map. Essentially, mapping an optional value gives you an unwrapped value in your closure, which you can then modify to return what you need. Outside the closure, you will receive either the modified value, or nil if the original optional was nil.
let optInt: Int? = 1 // or nil
let incremented: Int? = optInt.map { $0 + 1 }
// If optInt isn't nil, its incremented value is returned by map.
// If it is nil, map just returns nil.
So to solve my problem, I could do:
if text == "" || Int(text).map({$0 < 2}) ?? false {
// If text has an Int value, the map closure will return
// whether that value is less than 2.
// Else, map will return nil, which we coalesce to false.
}

Guard statement in swift for error handling [duplicate]

This question already has answers here:
How to check if a text field is empty or not in swift
(16 answers)
Closed 6 years ago.
I tried running below statement, but it's skipped right over in the code.
guard let num1 = num1Input.text else
{
show("No input in first box")
return
}
Can somebody tell me why this statement isn't running when the text field is blank?
You could use the where clause to check for non-nil and non-empty
guard let num1 = num1Input.text where !num1.isEmpty else {
show("No input in first box")
return
}
You should check the length of the text, not if its nil i.e:
guard num1Input.text.characters.count > 0 else {
...
}
If the text is optional, you can do.
guard let num1 = num1Input.text where num1.characters.count > 0 else {
...
}
This statement is testing whether the text is nil but you probably want to test whether the string is empty, therefore
guard let input = num1Input.text where input.characters.count > 0 else {
print("empty")
return
}
or just simply
guard num1Input.text?.characters.count > 0 else {
print("empty")
return
}

Looping through arrays and increasing counters in Swift

So I am trying to loop through an array and increase a two counters based on the index in the array. The array has boolean values in it and the counters are for said boolean values. My implementation is error filled, so I am trying to figure out whether my logic is incorrect or my implementation is incorrect. So an extra pair of eyes would help
var numTrue = 0
var numFalse = 0
var boolArray = [true, true, false, true]
for index in boolArray.enumerate() {
if index == false{
numFalse++
} else {
numTrue++
}
}
print("Count true: \(numTrue)")
print("Count false: \(numFalse)")
Like Unheilig wrote enumarate is returning a sequenceType.
You can also remove the enumerate call, which in my opinion make the code in your case more readable like this:
var numTrue = 0
var numFalse = 0
var boolArray = [true, true, false, true]
for index in boolArray {
if index == false{
numFalse++
} else {
numTrue++
}
}
print("Count true: \(numTrue)")
print("Count false: \(numFalse)")
Edit 1: Quick-Help Doc
For the next time If you have a similar problem or error popping up you can also use the Quick-Help Documentation by holding alt and hovering over the method than a questionmark appears and you can click on the method. A window will open with a description of the method and sometimes an example like in the case of enumerate(). See the screenshot below:
Edit 2: Improved Solution
Swift provides methods on collections in your case an array to reduce the amount of code.
In your case you can use the method filter() which returns a new array by filtering out elements from the array on which it's called. The only argument is a closure (read more about closures here) that returns a boolean and it will execute this closure once for each element in the array.
Swift automatically provides shorthand argument names to inline closures which can be used to refer to the values of the closure's arguments by the names $0, $1, $2 and so on (from the documentation).
So in your case $0 stands for each element beginning at index 0.
count returns the number of elements in you array, so in your case by using filter it only returns 3 because true appears 3 times. trueCounter = 3
For the falseCounter you can easily subtract the result of the trueCounter from boolArray.count which is falseCounter = 4 - 3 -> 1
var boolArray = [true, true, false, true]
let trueCounter = boolArray.filter({$0 == true}).count
let falseCounter = boolArray.count - trueCounter
print("Count true: \(trueCounter)") // Count true: 3
print("Count false: \(falseCounter)") // Count false: 1
The code does not compile because enumerate is returning you a SequenceType in form (n, x).
Change your code to the following:
var numTrue = 0
var numFalse = 0
let boolArray = [true, true, false, true]
//We are interested in value, ignoring the index
for (_, value) in boolArray.enumerate()
{
if value == false
{
numFalse++
}
else
{
numTrue++
}
}
Output:
Count true: 3
Count false: 1
Try this:
var numTrue = 0
var numFalse = 0
let boolArray = [true, true, false, true]
for index in boolArray {
if index {
numTrue += 1
} else {
numFalse += 1
}
}
print("Count true: \(numTrue)")
print("Count false: \(numFalse)")
Just to add some more swifty spice, here's a different approach.
numTrue = boolArray.filter{ $0 }.count
numFalse = boolArray.count - numTrue
It does have different runtime characteristics though (i.e. creates a new array on the filter operation) - so I'd not recommend it unconditionally.

Immutable array on a var?

I am getting the error:
Immutable value of type 'Array Character>' only has mutating members of name removeAtIndex()
The array should have contents because that removeAtIndex line is in a loop who's condition is if the count > 1
func evaluatePostFix(expression:Array<Character>) -> Character
{
var stack:Array<Character> = []
var count = -1 // Start at -1 to make up for 0 indexing
if expression.count == 0 {
return "X"
}
while expression.count > 1 {
if expression.count == 1 {
let answer = expression[0]
return answer
}
var expressionTokenAsString:String = String(expression[0])
if let number = expressionTokenAsString.toInt() {
stack.append(expression[0])
expression.removeAtIndex(0)
count++
} else { // Capture token, remove lefthand and righthand, solve, push result
var token = expression(count + 1)
var rightHand = stack(count)
var leftHand = stack(count - 1)
stack.removeAtIndex(count)
stack.removeAtIndex(count - 1)
stack.append(evaluateSubExpression(leftHand, rightHand, token))
}
}
}
Anyone have any idea as to why this is? Thanks!
Because all function parameters are implicitly passed by value as "let", and hence are constant within the function, no matter what they were outside the function.
To modify the value within the function (which won't affect the value on return), you can explicitly use var:
func evaluatePostFix(var expression:Array<Character>) -> Character {
...
}

Resources