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
}
}
Related
I have an easy question that is also hard at the same time. I have two separate structs (this can also work for classes):
struct FBTweet {
var tweetId: Int? //set
var tweetText: String? //set
}
and
struct Status {
var statusId: Int? //set
var statusText: String? //no value
}
I have an array of both structs var fbTweetArray: [FBTweet] = [] and var statusArray: [Status] = []
I have set every variable in to a certain value in each index in fbTweetArray but I only set the .statusId variable in each index for statusArray. For every statusArray.statusId value in statusArray, there is only one fbTweetArray.tweetId that has the same exact Int value. I am trying to make is so that if these two variables are the same then I should set set
statusArray.statusText to whatever fbTweetarray.tweetText is. So for example only fbTweetArray[1].tweetid = 2346 and statusArray[4].statusId = 2346 have 2346 as their value. There for if fbTweetArray[1].tweetText = "hello friend" then statusArray[4].statusText needs to be set to "hello friend".
So far I have
func testWhat () {
var fbTweetArray: [FBTweet] = []
var statusArray: [Status] = []
for fbTweet in fbTweetArray {
for var status in statusArray {
if (status.statusId == fbTweet.tweetId ) {
status.statusText = fbTweet.tweetText
}
}
}
}
how do I set the for var status in the for loop back into the statusArray since it is now a var and is different than one of the indexes in var statusArray: [Status] = []
Basically, you need only one for/forEach loop to achieve what you want:
var fbTweetArray: [FBTweet] = [
FBTweet(tweetId: 1, tweetText: "1"),
FBTweet(tweetId: 2, tweetText: "2"),
FBTweet(tweetId: 3, tweetText: "3")
]
var statusArray: [Status] = [
Status(statusId: 2, statusText: nil),
Status(statusId: 1, statusText: nil),
Status(statusId: 3, statusText: nil)
]
fbTweetArray.forEach { tweet in
if let index = statusArray.index(where: { $0.statusId == tweet.tweetId }) {
statusArray[index].statusText = tweet.tweetText
}
}
print(statusArray.map { $0.statusText }) // [Optional("2"), Optional("1"), Optional("3")]
Note, that your ids in both structures can be nil. To handle this situation (if both id is nil - objects are not equal) you can write custom == func:
struct Status {
var statusId: Int? //set
var statusText: String? //no value
static func == (lhs: Status, rhs: FBTweet) -> Bool {
guard let lhsId = lhs.statusId, let rhsId = rhs.tweetId else { return false }
return lhsId == rhsId
}
}
...
// rewrite .index(where: ) in if condition
if let index = statusArray.index(where: { $0 == tweet }) { ... }
Also, there is some pro-tip. If you adopt your structs to Hashable protocol, you will be able to place FBTweets and Statuses into Set structure. The benefits of that:
If you instead store those objects in a set, you can theoretically
find any one of them in constant time (O(1)) — that is, a lookup on a
set with 10 elements takes the same amount of time as a lookup on a
set with 10,000.
You can find more in-depth info about it in a new great article by NSHipster.
Your question is interesting only if both the arrays are not ordered.
To find the element from fbTweet array, you can sort it and employ binary search.
Then enumerate status array and find the fbTweet object with the same identifier and modify the status object. It needs to be saved again in the array as structs get copied on write.
extension Array where Element == FBTweet {
func binarySearchFBTweetWith(_ id:Int) -> FBTweet? {
var range = 0..<self.count
while range.startIndex < range.endIndex {
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
guard let tweetId = self[midIndex].tweetId else {
continue
}
if tweetId == id {
return self[midIndex]
} else if tweetId < id {
range = midIndex+1..<range.endIndex
} else {
range = range.startIndex..<midIndex
}
}
return nil
}
}
fbTweetArray.sort{($0.tweetId ?? 0) < ($1.tweetId ?? 0)}
for (index, status) in statusArray.enumerated() {
guard let statusId = status.statusId else {continue}
guard let fbTweet = fbTweetArray.binarySearchFBTweetWith(statusId) else {continue}
var status = status
status.statusText = fbTweet.tweetText
statusArray[index] = status
}
An alternative would be use dictionaries instead of arrays, for better performance and easier implementation (in this case). You can easily get the array of keys and values later If you need.
In this case, the Id would be the Key of the dictionary, and the text the value.
I tried to create a custom iterator which returns wrapper abcContainer over raw data class abc
// raw data class
class abc {
var name : String = "";
init( _ value : String) {
name = value;
}
}
// with container, only "name" is to be visible
class abcContainer {
private var _abc : abc;
init( _ obj : abc) {
_abc = obj;
}
// + extra methods here
func getName() -> String {
return _abc.name
}
}
The point would be that the dictionary would return instances of abcContainer instead of just the plain raw abc class.
I wanted to use the sequence protocol to make the conversion automatic, but I was not able to transform the [String:abc] into [String:abcContainer] automatically like this:
// the iterator is implemented just iterating the inner basic dict
// but wrapping the result value as abcContainer
class abcIterator : Sequence, IteratorProtocol {
private var __source : [String:abc]?;
var index = 0
var myIterator : DictionaryIterator<String, abc>;
init(_ ctxArray: [String:abc]) {
self.__source = ctxArray
index = 0;
myIterator = (__source?.makeIterator())!
}
func next() -> abcContainer? {
let nextItem = myIterator.next();
if(nextItem != nil) {
return abcContainer((nextItem?.value)!);
}
return nil;
}
}
// this was supposed to be the wrapper over the collection
class abcCollection : Sequence {
private var __source : [String:abc]?;
init(_ list: [String:abc]) {
self.__source = list
}
func makeIterator() -> abcIterator {
return abcIterator(self.__source!);
}
}
I'm probably missing something very basic here. When I try to use the collection like this:
var dict : [String:abc] = [String:abc]();
dict["abba"] = abc("John Smith");
for (key,value) in abcCollection(dict) {
print(key, value.getName());
}
I get error: Expression type "abcCollection" is ambiguous without more context
Does anyone have idea how to make it work? What is missing? I have a feeling that this answer has the information I need...
Swift 2 to 3 Migration for Swift Sequence Protocol
The problem in your original code is that abcCollection(dict)
returned a sequence of abcContainer objects, and those cannot
be assigned to a (key, value) tuple.
You can achieve your goal with
class abcCollection : Sequence {
private var __source : [String:abc]
init(_ list: [String:abc]) {
self.__source = list
}
public func makeIterator() -> AnyIterator<(AnyObject,abcContainer)> {
let mapped = self.__source.lazy.map {
($0.key as AnyObject, abcContainer($0.value))
}
return AnyIterator(mapped.makeIterator())
}
}
Making __source non-optional makes all the (optional) unwrappings
redundant, and lazy.map { ... } returns a lazily evaluated
sequence of key/value pairs which is then type-erased.
Ok, perhaps the answer was abcIterator was not necessary, you could have defined the iterator directly just like done in the linked answer like this:
class abcCollection : Sequence {
private var __source : [String:abc]?;
init(_ list: [String:abc]) {
self.__source = list
}
public func makeIterator() -> AnyIterator<(AnyObject,abcContainer)> {
var it = self.__source?.makeIterator();
return AnyIterator {
let n = it?.next();
if n == nil { return nil }
return (n?.key as AnyObject, abcContainer((n?.value)!))
}
}
}
After that, the custom collection returned wrapped objects correctly.
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
I have a dictionary that's updated from another class. I have a property observer on the dictionary so I know when a value has been added or removed.
I create a sorted array based on the values of the dictionary. I need to keep this array updated and retain the index associated with the update for use with a UITableView. My UI is as such that a wholesale reloading of data isn't possible - I need to directly insert or remove rows based on what the update was.
I have simplified this into a playground:
func dictionaryUpdated() {
print("dictionary updated")
// Add or remove string at index depending on order.
}
var myDictionary : [Int:String] = ["Bob".hashValue:"Bob","Dave".hashValue:"Dave","Yoda".hashValue:"Yoda","Windu".hashValue:"Windu","Obi Wan".hashValue:"Obi Wan","Qui-gon".hashValue:"Qui-gon","Anakin".hashValue:"Anakin"] { didSet { dictionaryUpdated() } }
func addEntry(entry: String) {
myDictionary[entry.hashValue] = entry
}
func removeEntry(entry: String) {
myDictionary.removeValueForKey(entry.hashValue)
}
// sort the keys alphabetically while creating the array
var valuesArray = myDictionary.values.sort { (lhs, rhs) -> Bool in
return lhs < rhs
}
I have tried using an NSMutableOrderedSet but the keys can only be Strings.
Just playing around in playground. Can be much more elegant though...
var valuesArray: [String] = [] { didSet { valuesArray.sortInPlace { $0 < $1 } } }
func dictionaryUpdated(old: [Int: String]) {
let added = myDictionary.count > old.count
let item: [String] = added ? myDictionary.values.filter { !old.values.contains($0) } : old.values.filter { !myDictionary.values.contains($0) }
valuesArray += item
let index = valuesArray.indexOf(item[0])!
print("item " + (added ? "added" : "removed") + ": \(item) at index \(index)")
}
var myDictionary: [Int: String] = ["Yoda".hashValue: "Yoda", "Windu".hashValue: "Windu", "Obi Wan".hashValue: "Obi Wan"] {
didSet {
dictionaryUpdated(oldValue)
}
}
addEntry("Darth Vader")
print(valuesArray)
Output:
item added: ["Darth Vader"] at index 0
["Darth Vader", "Obi Wan", "Windu", "Yoda"]
Assuming you have the sorted array before and after the property change (which can be achieved via another instance variable), what you need to do is to compare the old and the new array, and detect which which indexes changed.
An elegant solution to this problem would be to add a diff method to the Array class which computes the difference. The method might look something like this:
extension Array where Element: Equatable {
func diff(other: [Element]) -> (added: [Int], deleted: [Int], moved: [(from: Int, to: Int)]) {
var added: [Int] = []
var deleted: [Int] = []
var moved: [(from: Int, to: Int)] = []
for (i, item) in enumerate() {
if let j = other.indexOf({$0 == item}) {
if i != j {
moved.append((from: i, to: j))
}
} else {
deleted.append(i)
}
}
for (i, item) in other.enumerate() {
if indexOf({$0 == item}) == nil {
added.append(i)
}
}
return (added: added, deleted: deleted, moved: moved)
}
}
You would then use like this: valuesArray.diff(oldValuesArray).
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