Is there any pretty way to test the below? I have multiple parameters which I need to know if any one of them is nil
This is what I am using now, I am sure there is an efficient way to test all and type nil once but not sure how:
if title == nil || name == nil || height == nil || productName == nil {
//Do something
}
I am using ObjectMapper and at they moment, they don't support error handling, hence, my init() throws errors and I need to check if the values from Map are nil or not and through if they are.
I have created a simple extension on CollectionType to check for a collection of Optional value, if at least one element is not nil, if all elements have value or if none have value.
extension CollectionType where Generator.Element == Optional<AnyObject>, Index.Distance == Int {
func allNotNil() -> Bool {
return !allNil()
}
func atleastOneNotNil() -> Bool {
return self.flatMap { $0 }.count > 0
}
func allNil() -> Bool {
return self.flatMap { $0 }.count == 0
}
}
var title: String? = ""
var name: String? = ""
var height: Float? = 1
var productName: String? = ""
[title, name, height, productName].allNotNil()
[title, name, height, productName].atleastOneNotNil()
[title, name, height, productName].allNil()
In your case, you could use it like this,
if [title, name, height, productName].atLeastOneNotNil() {
}
Or, you could discard the extension above and simply use it like this,
if [title, name, height, productName].flatMap { $0 }.count > 0 {
}
For Swift 4,
extension Collection where Element == Optional<Any> {
func allNotNil() -> Bool {
return !allNil()
}
func atleastOneNotNil() -> Bool {
return self.flatMap { $0 }.count > 0
}
func allNil() -> Bool {
return self.flatMap { $0 }.count == 0
}
}
Updates for Swift 5,
Few new functions have been added to CollectionType such as first(where:) and allSatisfy(where:) and it is used here.
extension Collection where Element == Optional {
func allNil() -> Bool {
return allSatisfy { $0 == nil }
}
func anyNil() -> Bool {
return first { $0 == nil } != nil
}
func allNotNil() -> Bool {
return !allNil()
}
}
Here's a short version using a collection literal:
let isAnyNil = ([title, name, height, productName, nil] as [Optional<Any>]).contains { $0 == nil }
It's similar to #GeneratorOfOne's flatMap and count variant. I prefer the simplicity of contains.
If you do this often, I'd go with a free function to avoid the need to specify the type:
func isAnyNil(optionals: Optional<Any> ...) -> Bool {
return optionals.contains { $0 == nil }
}
isAnyNil(title, name, height, productName)
I'm not sure why you need to know, but if it is kind of unwrapping than it better to do so in Swift 2.0
if let email = emailField?.text, password = passwordField?.text {
//here you have both email & password
}
if you enter a method and need to do something in case any of them is nil, I would recommend using a guard:
guard let email = emailField?.text else {
// It is nil, do something
return
}
// if we got here, we have 'email' and it is not nil.
Side Note:
I'm guessing when you mean efficient you really talk about pretty or easy and not really efficient, because in either cases you would have to evaluate all arguments to see if they are nil.
If indeed you just want it to be pretty, you could use .filter to check
var nilElements = [email,password].filter{0 == nil}
you will get back only the elements which are nil
Related
i am currently working on an App that needs to compare three Variables with each other.
Rules for Comparison: The result should only be true if:
All three variables are equal OR All three variables are different
My first idea was something like this, but I hope there is a more elegant solution for this:
if (value1 == value2 && value2 == value3) || (value1 != value2 && value2 != value3 && value3 != value1) {
// True
} else {
// False
}
I would be really happy if someone of you can think of a more elegant solution and share it with me.
Thanks for your help in advance!
If your values are also Hashable you can use a Set. Given the fact that a Set discards duplicate values, you can simplify your check to something like this:
let valuesArray = [value1, value2, value3]
let valuesSet = Set(valuesArray)
if valuesSet.count == 1 || valuesSet.count == valuesArray.count {
// True
} else {
// False
}
For a one-off, that's not too bad.
The more "general" solution is kind of messy, because it needs to track 2 different boolean variables, and handle empty collections correctly.
extension Sequence where Element: Equatable {
func classifyElementEquality() -> (allEqual: Bool, allUnequal: Bool) {
var iterator = self.makeIterator()
guard let first = iterator.next() else {
return (true, true) // all empty
}
return AnyIterator(iterator)
.reduce(into: (allEqual: true, allUnequal: true)) { acc, element in
if first == element {
acc.allUnequal = false
} else {
acc.allEqual = false
}
}
}
}
let (value1, value2, value3) = (1, 2, 3)
let result = [value1, value2, value3].classifyElementEquality()
if result.allEqual || result.allUnequal {
print("They're either all equal, or all unequal")
} else {
print("Some of them are different")
}
It can get a bit simpler if this algorithm targets Collection insteaad of Sequence, because accessing the first element is easier without needing to manually manage an iterator.
extension Collection where Element: Equatable {
func classifyElementEquality() -> (allEqual: Bool, allUnequal: Bool) {
guard let first = self.first else {
return (true, true) // all empty
}
return self
.dropFirst()
.reduce(into: (allEqual: true, allUnequal: true)) { acc, element in
if first == element {
acc.allUnequal = false
} else {
acc.allEqual = false
}
}
}
}
I have a structure in my Swift app:
open class Cluster : NSObject {
open var username: String? = ""
open var id: String? = ""
open var deleted: Bool? = false
}
and now I'm iterating over this array and I'm adding new elements to it, but only in case those elements are not there yet:
if(!self.array.contains(where: {$0.id==temp.id}))
{
self.array.append(temp);
}
I want to tweak this code so that it not only adds new elements if they're not there, but also removes the ones that - in the meantime - had their flag deleted changed to true.
I started writing this code:
if(!self.array.contains(where: {$0.id==temp.id}))
{
self.array.append(temp);
} else {
if(temp.deleted == true){
self.array.remove //how can I remove here this specific element?
}
}
To remove a particular element from an array, you are supposed to get index of that element first and then delete as shown below:
if let index:Int = self.array.index(where: {$0.id == temp.id && $0.deleted == true}) {
self.array.remove(at: index)
}
First, I suggest you fix your class:
An optional Bool makes no sense - the object is either deleted or not
An optional id doesn't make much sense either; All objects need an id
If you implement the hash and equality parts of NSObject then you get access to array's index(of:) method and you can use sets.
Cluster.swift
open class Cluster : NSObject {
open var username: String? = ""
open let id: String
open var isDeleted: Bool = false
init(id: String) {
self.id = id
}
open override var hashValue: Int {
get {
return self.id.hashValue
}
}
open override func isEqual(_ object: Any?) -> Bool {
guard let rhs = object as? Cluster else {
return false
}
let lhs = self
return lhs.id == rhs.id
}
}
Now, given an array of Cluster objects, you can remove the deleted ones using:
let cleanArray = dirtyArrayOfCluster.filter {
!$0.isDeleted
}
And you can remove duplicates by passing the array through a set:
let deDupedArray = Array(Set(cleanArray))
if temp.deleted == true, let index = array.index(where: { $0.id == temp.id }) {
array.remove(at: index)
}
What about this?
if array.contains(where: { $0.id == temp.id } ) {
array.append(temp)
}
array = array.filter { $0.deleted == true }
The first part add temp only if it is not into the array.
The last line removes all the elements marked as deleted.
Is it possible to count all properties who are not nil?
For example:
class test {
var val1:Int?
var val2:Int?
var val3:Int?
var val4:Int?
var val5:Int?
}
var test = test()
test.val1 = 1
test.val2 = 2
How to find out that 2 properties are set? I could check for each one with (!= nil) - but is there an easier (and better) way?
You can do this manually, with a convenience method:
func numberOfNonNil() -> Int {
let vals = [val1, val2, val3, val4, val5]
return flatMap { $0 }.count
}
flatMap(_:) takes a closure that takes a single element of the array and returns an optional value (Element -> T?), and returns the result of applying that closure to each element of the array, with nil values ignored.
The only way to make this simpler would be to store your values as an array of optional Ints in the first place:
class Test {
var vals: [Int?]
}
You can then still access each individual value using the array subscript notation (let val2 = vals[1]). You could then just use the second line in the convenience method above (filter then count) to get the number of non-nil values:
let nonNilCount = vals.flatMap { $0 }.count
If your values are of different types, this approach will still work if you cast the array to a type that encompasses all the different types:
class Test {
var val1: Int?
var val2: Double
var val3: String
var val4: MyRandomClass?
func numberOfNonNil() -> Int {
let vals = [val1, val2, val3, val4, val5] as [Any?]
return flatMap { $0 }.count
}
}
This works because all the values can be expressed as the type Any?.
I don't think there's a way to do this, but you can implement your own function like this:
class test {
var val1:Int?
var val2:Int?
var val3:Int?
var val4:Int?
var val5:Int?
func setVarsCount() -> Int {
var setVariablesCount = 0
if val1 != nil {
setVariablesCount++
}
if val2 != nil {
setVariablesCount++
}
if val3 != nil {
setVariablesCount++
}
if val4 != nil {
setVariablesCount++
}
if val5 != nil {
setVariablesCount++
}
return setVariablesCount
}
}
#Stuarts answer is good, but you have to know the properties of the class, and if you add another property to the class, you also have to modify your method. To avoid this problem, you can use reflection, like
Swift 1.2:
func numberOfOptionalProperties() -> Int {
let mirror = reflect(self)
var numberOfOptionalProperties = 0
for index in 0..<mirror.count {
if mirror[index].1.disposition == .Optional {
++numberOfOptionalProperties
}
}
return numberOfOptionalProperties
}
Swift 2.0:
func numberOfOptionalProperties() -> Int {
return Mirror(reflecting: self).children.reduce(0) {
Mirror(reflecting: $1.value).displayStyle == .Optional ? $0 + 1 : $0
}
}
How can we compare two strings in swift ignoring case ?
for eg :
var a = "Cash"
var b = "cash"
Is there any method that will return true if we compare var a & var b
Try this :
For older swift:
var a : String = "Cash"
var b : String = "cash"
if(a.caseInsensitiveCompare(b) == NSComparisonResult.OrderedSame){
println("Et voila")
}
Swift 3+
var a : String = "Cash"
var b : String = "cash"
if(a.caseInsensitiveCompare(b) == .orderedSame){
print("Et voila")
}
Use caseInsensitiveCompare method:
let a = "Cash"
let b = "cash"
let c = a.caseInsensitiveCompare(b) == .orderedSame
print(c) // "true"
ComparisonResult tells you which word comes earlier than the other in lexicographic order (i.e. which one comes closer to the front of a dictionary). .orderedSame means the strings would end up in the same spot in the dictionary
if a.lowercaseString == b.lowercaseString {
//Strings match
}
Try this:
var a = "Cash"
var b = "cash"
let result: NSComparisonResult = a.compare(b, options: NSStringCompareOptions.CaseInsensitiveSearch, range: nil, locale: nil)
// You can also ignore last two parameters(thanks 0x7fffffff)
//let result: NSComparisonResult = a.compare(b, options: NSStringCompareOptions.CaseInsensitiveSearch)
result is type of NSComparisonResult enum:
enum NSComparisonResult : Int {
case OrderedAscending
case OrderedSame
case OrderedDescending
}
So you can use if statement:
if result == .OrderedSame {
println("equal")
} else {
println("not equal")
}
localizedCaseInsensitiveContains : Returns whether the receiver contains a given string by performing a case-insensitive, locale-aware search
if a.localizedCaseInsensitiveContains(b) {
//returns true if a contains b (case insensitive)
}
Edited:
caseInsensitiveCompare : Returns the result of invoking compare(_:options:) with NSCaseInsensitiveSearch as the only option.
if a.caseInsensitiveCompare(b) == .orderedSame {
//returns true if a equals b (case insensitive)
}
CORRECT WAY:
let a: String = "Cash"
let b: String = "cash"
if a.caseInsensitiveCompare(b) == .orderedSame {
//Strings match
}
Please note: ComparisonResult.orderedSame can also be written as .orderedSame in shorthand.
OTHER WAYS:
a.
if a.lowercased() == b.lowercased() {
//Strings match
}
b.
if a.uppercased() == b.uppercased() {
//Strings match
}
c.
if a.capitalized() == b.capitalized() {
//Strings match
}
Could just roll your own:
func equalIgnoringCase(a:String, b:String) -> Bool {
return a.lowercaseString == b.lowercaseString
}
For Swift 5
Ignoring the case and compare two string
var a = "cash"
var b = "Cash"
if(a.caseInsensitiveCompare(b) == .orderedSame){
print("Ok")
}
Phone numbers comparison example; using swift 4.2
var selectPhone = [String]()
if selectPhone.index(where: {$0.caseInsensitiveCompare(contactsList[indexPath.row].phone!) == .orderedSame}) != nil {
print("Same value")
} else {
print("Not the same")
}
You can just write your String Extension for comparison in just a few line of code
extension String {
func compare(_ with : String)->Bool{
return self.caseInsensitiveCompare(with) == .orderedSame
}
}
Swift 4, I went the String extension route using caseInsensitiveCompare() as a template (but allowing the operand to be an optional). Here's the playground I used to put it together (new to Swift so feedback more than welcome).
import UIKit
extension String {
func caseInsensitiveEquals<T>(_ otherString: T?) -> Bool where T : StringProtocol {
guard let otherString = otherString else {
return false
}
return self.caseInsensitiveCompare(otherString) == ComparisonResult.orderedSame
}
}
"string 1".caseInsensitiveEquals("string 2") // false
"thingy".caseInsensitiveEquals("thingy") // true
let nilString1: String? = nil
"woohoo".caseInsensitiveEquals(nilString1) // false
Swift 3: You can define your own operator, e.g. ~=.
infix operator ~=
func ~=(lhs: String, rhs: String) -> Bool {
return lhs.caseInsensitiveCompare(rhs) == .orderedSame
}
Which you then can try in a playground
let low = "hej"
let up = "Hej"
func test() {
if low ~= up {
print("same")
} else {
print("not same")
}
}
test() // prints 'same'
You could also make all the letters uppercase (or lowercase) and see if they are the same.
var a = “Cash”
var b = “CASh”
if a.uppercaseString == b.uppercaseString{
//DO SOMETHING
}
This will make both variables as ”CASH” and thus they are equal.
You could also make a String extension
extension String{
func equalsIgnoreCase(string:String) -> Bool{
return self.uppercaseString == string.uppercaseString
}
}
if "Something ELSE".equalsIgnoreCase("something Else"){
print("TRUE")
}
Swift 3
if a.lowercased() == b.lowercased() {
}
Swift 3:
You can also use the localized case insensitive comparison between two strings function and it returns Bool
var a = "cash"
var b = "Cash"
if a.localizedCaseInsensitiveContains(b) {
print("Identical")
} else {
print("Non Identical")
}
extension String
{
func equalIgnoreCase(_ compare:String) -> Bool
{
return self.uppercased() == compare.uppercased()
}
}
sample of use
print("lala".equalIgnoreCase("LALA"))
print("l4la".equalIgnoreCase("LALA"))
print("laLa".equalIgnoreCase("LALA"))
print("LALa".equalIgnoreCase("LALA"))
I have 2 arrays:
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
I want to determine if list Array contains all findList elements.
By the way, elements might be String as well or other type.
How to do that?
I know that Swift provides contains method that works with one item.
Instead of iterating through arrays and doing filtering yourself, you can use NSSet to do all the work for you.
var list:Array<Int> = [1,2,3,4,5]
var findList:Array<Int> = [1,3,5]
let listSet = NSSet(array: list)
let findListSet = NSSet(array: findList)
let allElemtsEqual = findListSet.isSubsetOfSet(otherSet: listSet)
NSSet is a lot faster than arrays at checking if it contains any object. In fact it's what it's designed for.
Edit: Using Swift's built-in Set.
let list = [1,2,3,4,5]
let findList = [1,3,5]
let listSet = Set(list)
let findListSet = Set(findList)
//**Swift 4.2 and Above**
let allElemsContained = findListSet.isSubset(of: listSet)
//below versions
//let allElemsContained = findListSet.isSubsetOf(listSet)
allSatisfy seems to be what you want, assuming you can't conform your elements to Hashable and use the set intersection approach others have mentioned:
let containsAll = subArray.allSatisfy(largerArray.contains)
Since Swift 4.2 you can write:
extension Array where Element: Equatable {
func satisfy(array: [Element]) -> Bool {
return self.allSatisfy(array.contains)
}
}
Otherwise for Swift 3, Swift 4 you can write this:
extension Array where Element: Equatable {
func contains(array: [Element]) -> Bool {
for item in array {
if !self.contains(item) { return false }
}
return true
}
}
You can see the:
contains method here
allSatisfy method here
This is just a simple extension that check if the array that you give is in the current array (self)
You can use the filter method to return all elements of findList which are not in list:
let notFoundList = findList.filter( { contains(list, $0) == false } )
then check if the length of the returned array is zero:
let contained = notFoundList.count == 0
Note that his solution traverses the entire findList array, so it doesn't stop as soon as a non contained element is found. It should be used if you also want to know which elements are not contained.
If you just need a boolean stating whether all elements are contained or not, then the solution provided by Maxim Shoustin is more efficient.
Consider following generic method:
func arrayContainsArray<S : SequenceType where S.Generator.Element : Equatable>
(src:S, lookFor:S) -> Bool{
for v:S.Generator.Element in lookFor{
if contains(src, v) == false{
return false
}
}
return true
}
The advantage - method stops after 1st fail and do not continue over findList
Tests
var listAsInt:Array<Int> = [1,2,3,4,5]
var findListAsInt:Array<Int> = [1,3,5]
var result = arrayContainsArray(listAsInt, findListAsInt) // true
listAsInt:Array<Int> = [1,2,3,4,5]
findListAsInt:Array<Int> = [1,3,5,7,8,9]
result = arrayContainsArray(listAsInt, findListAsInt) // false
var listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
var findListOfStr:Array<String> = ["bbb","ccc","eee"]
result = arrayContainsArray(listOfStr, findListOfStr) // true
listOfStr:Array<String> = ["aaa","bbb","ccc","ddd","eee"]
findListOfStr:Array<String> = ["bbb","ccc","eee","sss","fff","ggg"]
result = arrayContainsArray(listOfStr, findListOfStr) // false
(tested on Beta7)
As a complement to Sequence.contains(element) handling multiple elements, add this extension:
public extension Sequence where Element : Hashable {
func contains(_ elements: [Element]) -> Bool {
return Set(elements).isSubset(of:Set(self))
}
}
Used:
list.contains(findList)
Since this uses Set/Hashable it performs much better than Equatable alternatives.
Right now, I'd probably use something like:
let result = list.reduce(true, { $0 ? contains(findList, $1) : $0 })
...but then I did just read this article, which might be biasing me towards this kind of solution. You could probably make this more efficient without making it completely unreadable, but it's early and I've not had my coffee.
Extend the Array with the following methods:
extension Array {
func contains<T where T : Equatable>(obj: T) -> Bool {
return self.filter({$0 as? T == obj}).count > 0
}
func isEqualTo< T : Equatable> (comparingArray : [T]) -> Bool {
if self.count != comparingArray.count {
return false
}
for e in comparingArray {
if !self.contains(e){
return false
}
}
return true
}
}
An example of how you can use it like this:
if selectedDates.isEqualTo(originalDates) {
//Arrays the same hide save button
} else {
//Arrays not the same, show Save & Discard Changes Button (if not shown)
}
Shout out to #David Berry for the contain method.
None of the previous answers seem to be right.
consider:
let a = [2,2]
let b = [1,2,3]
we wouldn't say that b actually "contains" a, but if your algorithm is based on for-loop & swift's built-in contains(element:) or a set, the above case would pass.
I use this set of extended methods myself. I hope this code snippet helps:
// Array + CommonElements.swift
import Foundation
public extension Array where Element: Hashable {
func set() -> Set<Array.Element> {
return Set(self)
}
func isSubset(of array: Array) -> Bool {
self.set().isSubset(of: array.set())
}
func isSuperset(of array: Array) -> Bool {
self.set().isSuperset(of: array.set())
}
func commonElements(between array: Array) -> Array {
let intersection = self.set().intersection(array.set())
return intersection.map({ $0 })
}
func hasCommonElements(with array: Array) -> Bool {
return self.commonElements(between: array).count >= 1 ? true : false
}
}
This is Maxim Shoustin's answer updated for Swift 3:
func arrayContainsArray<S : Sequence>
(src:S, lookFor:S) -> Bool where S.Iterator.Element : Equatable{
for v:S.Iterator.Element in lookFor{
if src.contains(v) == false{
return false
}
}
return true
}
If you need to determine, that one array is subArray of another.
public extension Array where Element: Equatable {
func isSuperArray(of array: Array<Element>) -> Bool {
guard
count >= array.count,
let indexes = array.first.flatMap(indexes(of:)),
!indexes.isEmpty else {
return false
}
let arraysForComparison = indexes
.compactMap { index -> [Element]? in
guard index + (array.count - 1) <= count else { return nil }
return Array(self[index..<(index + array.count)])
}
return arraysForComparison.contains(array)
}
func isSubArray(of array: Array<Element>) -> Bool {
array.isSuperArray(of: self)
}
private func indexes(of element: Element) -> [Index] {
enumerated()
.filter { element == $0.1 }
.map { index, _ in index }
}
}
Example of usage:
let array1 = [1, 2, 3, 4]
let array2 = [2, 3]
print(array1.isSuperArray(of: array2)) // true
print(array2.isSubArray(of: array1)) // true
print(array2.isSuperArray(of: array1)) // false
print(array1.isSubArray(of: array2)) // false