Pass a conditional as an argument in Swift? - swift

I was thinking of abstracting some conditional logic in my application. Suppose I have two functions in this simplified example:
func1(val1: Int, val2: Int, threshold: Int) -> Bool {
return (val1 == threshold && val2 == threshold)
}
func2(val: Int, thresholdHi: Int, thresholdLo: Int) {
return (val < thresholdHi && val > thresholdLo)
}
My idea is to make a function that performs a conditional check on a set of values.
funcIdea(vals[Int], conditional: ???) -> Bool {
for val in vals {
if !conditional(val) { return false; }
}
return true
}
func1(...){
return funcIdea([val1, val2], ???)
}
I think this is possible with either a closure or a function.

It is possible with a closure.
func funcIdea(vals: [Int], conditional: (Int)->Bool) -> Bool {
for val in vals {
if !conditional(val) { return false; }
}
return true
}
func func1(val1: Int, val2: Int, threshold: Int) -> Bool {
//return (val1 == threshold && val2 == threshold)
return funcIdea(vals: [val1, val2], conditional: { (val) -> Bool in
return val > threshold
})
}

You need to pass in a closure to your function, then you can use contains(where:) to check if the closure is true for all elements of the array. The double negation is necessary to allow for an early exit in contains(where:).
extension Array {
func allElementsSatisfy(_ condition: (Element)->(Bool)) -> Bool {
return !self.contains(where: { !condition($0)})
}
}
Then you can simply call it on any Array like this:
[1,2,3].allElementsSatisfy({$0 < 1}) // false
[1,2,3].allElementsSatisfy({$0 < 4}) // true
["a","b","c"].allElementsSatisfy({$0.count == 1}) // true
["a","b","c"].allElementsSatisfy({$0.count > 1}) // false

Related

Why does using a parameter name other the index result in Ambiguous use of 'subscript(_:)'

Take the two snippets below, the top one works fine but the bottom one results in
Ambiguous use of 'subscript(_:)'
using index ✅
extension Array {
subscript(i index: Int) -> (String, String)? {
guard let value = self[index] as? Int else {
return nil
}
switch (value >= 0, abs(value % 2)) {
case (true, 0): return ("positive", "even")
case(true, 1): return ("positive", "odd")
case(false, 0): return ("negative", "even")
case(false, 1): return ("negative", "odd")
default: return nil
}
}
}
Without using index ❌
extension Array {
subscript(i: Int) -> (String, String)? {
guard let value = self[i] as? Int else {
return nil
}
switch (value >= 0, abs(value % 2)) {
case (true, 0): return ("positive", "even")
case(true, 1): return ("positive", "odd")
case(false, 0): return ("negative", "even")
case(false, 1): return ("negative", "odd")
default: return nil
}
}
}
First, the name index is irrelevant to the problem; it could be any name.
The actual problem is that Array already has a subscript that takes an unlabeled Int.
Your first overload does not have the same input signature. Instead, it requires an argument label:
[1][i: 0] // ("positive", "odd")
You can still use an overload without a label…
extension Array where Element: BinaryInteger {
subscript(🫵: Int) -> (String, String) {
let value: Element = self[🫵]
return (
value >= 0 ? "positive" : "negative",
value.isMultiple(of: 2) ? "even" : "odd"
)
}
}
…but then, as is necessary within the subscript body itself, you'll need to always explicitly type the result, in order to access the overload from the standard library, because whatever you have in your own module is going to take precedence.
[1][0] // ("positive", "odd")
[1][0] as Int // 1
So, I recommend either using a subscript with a label, or a method.*
* What I would like to recommend is a named subscript. But Swift doesn't have them. You can emulate them with more types, however. Like this:
extension Array where Element: BinaryInteger {
struct InfoStrings {
fileprivate let array: Array
}
var infoStrings: InfoStrings { .init(array: self) }
}
extension Array.InfoStrings {
subscript(index: Int) -> (String, String) {
let value = array[index]
return (
value >= 0 ? "positive" : "negative",
value.isMultiple(of: 2) ? "even" : "odd"
)
}
}
[1].infoStrings[0]

Swift subscript with generic data type

I am trying to program a two-dimensional data storage struct for various data types. However, I am struggling with the subscript for setting the data due 'Cannot assign value of type 'T' to subscript of type 'T' errors. Any help is much appreciated!
struct dataMatrix<T> : Sequence, IteratorProtocol {
var rows: Int, columns: Int
var data: [T]
var position = 0
init(rows: Int, columns: Int) {
self.rows = rows
self.columns = columns
data = Array<T>()
}
func valueAt(column: Int, row: Int) -> T? {
guard column >= 0 && row >= 0 && column < columns else {
return nil
}
let indexcolumn = column + row * columns
guard indexcolumn < data.count else {
return nil
}
return data[indexcolumn]
}
}
subscript<T>(column: Int, row:Int) -> T?{
get{
return valueAt(column: column, row: row) as? T
}
set{
data[(column * row) + column] = (newValue as! T) // does not compile
}
}
// sequence iterator protorocl methods
mutating func next() -> String? {
if position <= data.count{
print(position)
defer { position += 1 }
return "\(position)"
}else{
defer {position = 0}
return nil
}
}
}
subscript<T>(column: Int, row:Int) -> T?{
defines a generic method with a type placeholder T which is unrelated to the generic type T of struct dataMatrix<T>. The solution is simple: Remove the type placeholder:
subscript(column: Int, row: Int) -> T? {
// ...
}
That makes also the type casts inside the getter and setter unnecessary. You only have to decide what to do if the setter is called with a nil argument (e.g.: nothing):
subscript(column: Int, row: Int) -> T? {
get {
return valueAt(column: column, row: row)
}
set {
if let value = newValue {
data[(column * row) + column] = value
}
}
}
Another option is to make the return type of the subscript method non-optional, and treat invalid indices as a fatal error (which is how the Swift Array handles it):
subscript(column: Int, row: Int) -> T {
get {
guard let value = valueAt(column: column, row: row) else {
fatalError("index out of bounds")
}
return value
}
set {
data[(column * row) + column] = newValue
}
}

How to have a function returning 1 value in some circumstances return 2 or 3 values and vice-versa?

I'm new to Swift. Is it possible to have a function return by standard only 1 value and in some circumstances 2 or 3 values ?
You can return a tuple:
func functionThatReturnsTuple(numberOfElementsToReturn: Int) -> (String?, Int?) {
if numberOfElementsToReturn == 1 {
return ("One", nil)
}
if numberOfElementsToReturn == 2 {
return ("One", 2)
}
return (nil, nil)
}
You can return array:
func functionThatReturnsArray(numberOfElementsToReturn: Int) -> [String] {
if numberOfElementsToReturn == 3 {
return ["One", "Two", "Three"]
}
....
return []
}
Notice that both examples use optionals, make sure to handle them .
Alternatively you can use enum:
enum Value<A, B, C> {
case single(A)
case pair(A, B)
case triplet(A, B, C)
}
func yourFunc<A, B, C>(numberOfParameters: Int) -> Value<A, B, C> {
// determine how many params you need to return, and return them
}
And inn your outer code just check Value's case with switch of if case.

Swift zip generator only iterates one side twice

I use zip on two arrays of tuples. When I iterate the result, the result tuple contains tuples from the left-hand side only.
Interestingly, Array(zip(...)) produces a collection as expected. I want to save a few cycles and memory and prefer not to generate a new array just for the sake of looping.
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 0)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for (l, r) in zip(lhs, rhs) {
// Looking at `l` and `r` in lldb shows both are the same.
if l.0 != r.0 || Int(l.1) != r.1 {
return false
}
}
return true
}
let equals = (expectation == comparison) // true?!?!
This was meant to be a convenience method to compare records of function calls in a test double to test data from the actual test cases. The double records (String, UInt), typing tuples in test cases produces (String, Int), so I thought: let's create an easy equality function! Changing UInt to Int doesn't change a thing.
How's that? Renders zip pretty useless to me (except when you can explain what's going on).
Can’t decide whether this is a bug or just something I’m not understanding (suspect bug but need to play with it more).
However, here’s a workaround in the mean-time, which involves fully destructuring the data:
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 1)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for ((ls, li),(rs,ri)) in zip(lhs, rhs) {
// Looking at `l` and `r` in lldb shows both are the same.
if ls != rs || Int(li) != ri {
return false
}
}
return true
}
let equals = (expectation == comparison) // now returns false
In theory, this ought to be more easily written as:
equal(expectation, comparison) {
$0.0 == $1.0 && Int($0.1) == $1.1
}
except that, infuriatingly, the equal function that takes a predicate still requires the elements of the two sequences to be the same! Radar 17590938.
A quick fix for this specific to arrays could look like:
func equal<T,U>(lhs: [T], rhs: [U], isEquivalent: (T,U)->Bool) -> Bool {
if lhs.count != rhs.count { return false }
return !contains(zip(lhs, rhs)) { !isEquivalent($0) }
}
// now the above use of equal will compile and return the correct result
p.s. you might want to add an overflow check for the UInt conversion
In support to #Airspeed Velocity, it seems a bug since you can define only the first tuple, than the second will work as expected :-/
Xcode 6.3.2 Swift 1.2
let expectation: [(String, UInt)] = [("bar", 0)]
let comparison: [(String, Int)] = [("foo", 1)]
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
for ((l0, l1), r) in zip(lhs, rhs) {
// Explicitly detiled first tuple
if l0 != r.0 || Int(l1) != r.1 {
return false
}
}
return true
}
let equals = (expectation == comparison) // false as expected
Yeah, looks like a bug. Weirdly, though, if you replace the for loop with contains(), you don't need to deconstruct the tuple, and it works like you'd expect:
func ==(lhs: [(String, UInt)], rhs: [(String, Int)]) -> Bool {
if lhs.count != rhs.count {
return false
}
return !contains(zip(lhs, rhs)) {
l, r in l.0 != r.0 || Int(l.1) != r.1
}
}

Function of swift, taking another function of its arguments

func hasAnyMatches(list: Int[], condition: Int -> Bool) -> Bool {
for item in list {
if condition(item) {
return true
}
}
return false
}
func lessThanTen(number: Int) -> Bool {
return number < 10
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanTen)
This is the example on The Swift Programming Language.
But in this example, I want to change
func lessThanTen(number: Int) -> Bool {
return number < 10
}
to
func lessThanBenchmark(number: Int, benchMark: Int) -> Bool {
return number < 10
}
so I also change this example to
func hasAnyMatches(list: Int[], condition: (Int, benchmark: Int) -> Bool) -> Bool {
for item in list {
if condition(item, benchmark) {
return true
}
}
return false
}
func lessThanBenchmark(number: Int, benchmark: Int) -> Bool {
return number < benchmark
}
var numbers = [20, 19, 7, 12]
hasAnyMatches(numbers, lessThanBenchmark)
However, I got an error.
<REPL>:155:28: error: use of unresolved identifier 'benchmark'
if condition(item, benchmark) {
At last, I have to write like this style
func hasAnyMatches(list: Int[], condition: (Int, Int) -> Bool, benchmark:Int) -> Bool {
for item in list{
if condition(item, benchmark) {
return true
}
}
return false
}
func lessThanBenchmark(number: Int, benckmark: Int) -> Bool {
return number < benckmark
}
var numbers = [20, 19, 7,12]
hasAnyMatches(numbers, lessThanBenchmark, 10)
How can I pass benchmark to condition just as its own parameter?
benchmark is an argument of function.
condition: (Int, benchmark: Int) -> Bool
this line of code means that condition is a function that return Bool value and can takes two arguments: Int as first argument, and benchmark: Int is second argument.
You can call benchmark inside of condition.
smth like that:
func hasAnyMatches(list: Int[], condition: (Int, benchmark: Int) -> Bool) -> Bool {
for item in list {
if condition(item, benchmark: 15) {
return true
}
}
return false
}
15 is a value that you want to use