I have an array of strings, and I want to do something only if my array contains specific things. Is there a shorter/better way to write the following other than repeating && multiple times?
var array = ["red", "purple", "orange", "yellow", "pink", "green", "blue"]
Here is a more advanced solution that does exactly the same as your solution (okay it creates an extra array instead of individual strings, but it computes the same value).
if ["red", "blue", "green", "yellow"].allSatisfy(array.contains) {
print("works")
}
We can write the operation out in its full form.
let elements = ["red", "blue", "green", "yellow"]
let containsAll = elements.allSatisfy { (element: String) -> Bool in
return array.contains(element)
}
if containsAll {
print("works")
}
Also note that array.contains(_:) is an O(n) operation because it will iterate the whole array over and over to check if the elements are present.
I'm no expert in things like performance costs for copying collections, but you can "potentially" optimize the speed (I leave the measurement to other people).
let set = Set(array)
if ["red", "blue", "green", "yellow"].allSatisfy(set.contains) {
print("works")
}
In this case set.contains(_:) is an O(1) operation.
I could read your answer before you removed it, if you do that you get a false result because it requires the inner contains return true once to complete the outer contains. You probably meant the same thing as I wrote above allSatisfy on inverted collections (switched the positions).
I had misread the original question as "or" instead of "and".
I think the best solution to the "and" version is actually imperative, to allow finishing early so it is not always necessary to walk the entire array even once:
var itemsOfInterest: Set = ["red", "blue", "green", "yellow"]
for item in array {
itemsOfInterest.remove(item)
if itemsOfInterest.isEmpty { break }
}
if itemsOfInterest.isEmpty {
// the array contains every item from the set
}
And for completeness, here's a solution to the "or" version:
let itemsOfInterest: Set = ["red", "blue", "green", "yellow"]
let arrayIsInteresting = array.contains{ itemsOfInterest.contains($0) }
if arrayIsInteresting {
// the array contains at least one item from the set
}
Edit:
The "and" version could also be done with subtract, though I don’t know off the top of my head if it finishes early when possible:
if itemsOfInterest.subtracting(array).isEmpty {
// the array contains every item from the set
}
In comparison isSuperset(of:) requires O(m) operations where m is the number of elements in your itemsOfInterest. The last solution I presented above with the set is basically the same as isSuperset(of:).
Why would it walk the entire array? Isn't it copied into a new set then? The comparison operations are still O(m) but you get some copying overhead, or am I wrong?
let requiredList = ["red", "green", "blue"]
let array = ["red", "purple", "orange", "yellow", "pink", "green", "blue"]
let isSatified = !requiredList.map { array.contains($0) }.contains(false)
//1. Will check if each element in requiredList is contained in the array. This will result in an array of boolean values
//2. See if the result from step1 contains a false
//3. Negate the result in step2, and that will tell you if it was satisfied.
print("isSatified = \(isSatified)")
I think that lazy will make the map lazily evaluated, and make the .contains(false) terminate once it finds a false, without evaluating the remaining mapping transforms (I.e the inner .contains).
However, it will also make the closure escaping and probably cause heap allocations(?)
I might be mistaken, and Swift may be able to optimize it, but .map on a lazy sequence needs its transform closure to be escaping and stored for later execution, as the actual mapping is delayed until the sequence needs to be walked. This allocation I think will happen on the heap. It also means that any variables captured by the stack, must also be allocated on the heap, if I am not mistaken. This means that the array on which you call array.contains($0) is also stored on the heap. Only when you call the latter .contains(false) will the lazily mapped sequence actually be walked, and the closure will be executed until it returns false. Only then will the lazy sequence be deallocated, and its heap allocations with it.