Replacing Element in a Set if a condition is met - ios

I'm working with a Set of CarCagetory:
public struct Images {
static let categoryTeaser = UIImage()
}
public enum PremiumModel {
case model1, model2, model3, unknown
}
public struct CarCategory {
public let teaserImage: UIImage
public let make: String
public let model: PremiumModel
public let startPrice: Double
}
// MARK: - Equatable
extension CarCategory: Equatable {
public static func == (lhs: CarCategory, rhs: CarCategory) -> Bool {
return lhs.model == rhs.model
}
}
// MARK: - Hashable
extension CarCategory: Hashable {
public var hashValue: Int {
return model.hashValue
}
}
I'm iterating over an array of Cars and extracting a Set of categories according to the model:
var categories = Set<CarCategory>()
carSpecifications.forEach {
if PremiumModel(rawValue: $0.car.model) != .unknown {
categories.insert(CarCategory(teaserImage: Images.categoryTeaser, make: $0.car.make, model: PremiumModel(rawValue: $0.car.model), startPrice: $0.pricing.price))
}
}
This works just fine.
Now I want to keep my Set updated with the lowest price for a certain model. I'm thinking on a dictionary of [PremiumModel: Double] where I keep the lowest price for a model and at the end I update my Set accordingly, but I wonder if there is a better way.
Edit:
That's my current implementation using a dictionary. It feels rudimentary...
carSpecifications.forEach {
let model = PremiumModel(rawValue: $0.car.model)
if model != .unknown {
if let value = minPriceForModel[model] {
minPriceForModel[model] = min(value, $0.pricing.price)
} else {
minPriceForModel[model] = $0.pricing.price
}
categories.insert(CarCategory(teaserImage: Images.categoryTeaser, make: $0.car.make, model: model, startPrice: $0.pricing.price))
}
}
let sortedCategories = Array(categories.sorted(by: <))
.compactMap { (category: CarCategory) -> CarCategory in
var newCategory = category
newCategory.startPrice = minPriceForModel[category.model] ?? 0
return newCategory
}
return sortedCategories

Related

Swift - toggle model to readonly momentarily

I have a phone number model which looks like this:
import UIKit
import Foundation
struct PhoneValidation : OptionSet {
let rawValue: Int
static let phoneInValid = PhoneValidation(rawValue: 1 << 0)
static let phoneValid = PhoneValidation(rawValue: 1 << 1)
static let smsValidationAttempted = PhoneValidation(rawValue: 1 << 2)
static let smsValidationFailed = PhoneValidation(rawValue: 1 << 3)
static let smsValidationSuccessful = PhoneValidation(rawValue: 1 << 4) // OTP is successfully validated in backend. The field should be non-editable in this duration
static let smsValidationOTPTriggered = PhoneValidation(rawValue: 1 << 5) // OTP validation triggered. The field should be non-editable in this duration
}
class PhonesViewModel: NSCopying {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}
As you can see above the phone model can transition between different states. The SMS validation is available for few countries and for few it is not applicable. So, I plan on setting smsValidationOTPTriggered state when SMS validation is applicable for a country and while the validation is in progress.
What I need here is, while the states smsValidationOTPTriggered or smsValidationSuccessful are set I would not want any module of the application to modify the values(phoneType, phone, code, countryCode) of the model. In other words, I would like the model to switch to a read-only mode while these 2 states are set in model and would like the module to be informed with an error or exception when a modification is attempted.
Is there a best practice already available for what I am trying to achieve here? I have searched before raising this question but did not find any. How can I achieve this?
Thanks,
Raj Pawan Gumdal
How about something like this, I think its better to use property wrappers for your case! The below is not an exact solution but can modify/change to accommodate your need
import UIKit
enum PhoneNumberType {
case mobile
}
enum PhoneValidation {
case phoneInValid
case phoneValid
case smsValidationAttempted
case smsValidationFailed
case smsValidationSuccessful
case smsValidationOTPTriggered
}
struct PhonesViewModel {
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
public var phone: String?
public var code: String?
public var countryCode: String?
public var phoneValidation : PhoneValidation?
func validate(value: [PhoneValidation]) -> Bool {
//add proper check here
return false
}
}
#propertyWrapper
struct Wrapper {
private(set) var value: PhonesViewModel? = nil
var validators: [PhoneValidation] = []
var wrappedValue: PhonesViewModel? {
get { value }
set {
if let model = newValue, model.validate(value: validators) {
value = newValue
print("Value assigned")
} else {
print("Value not assigned")
}
}
}
}
struct SomeOtherClass {
#Wrapper(validators: [PhoneValidation.phoneInValid])
var model: PhonesViewModel?
}
var a = SomeOtherClass()
a.model = PhonesViewModel()
a.model = PhonesViewModel()
You can use a technique with the name "popsicle immutability". An object is initially mutable, but can be "frozen". Modifications for frozen objects are forbidden. In your case PhonesViewModel become frozen when isValid property have value smsValidationOTPTriggered or smsValidationSuccessful.
Let's add Freezable protocol for requirements to objects that can become immutable and conforming for PhonesViewModel:
protocol Freezable: class {
var isFrozen: Bool { get }
}
extension PhonesViewModel: Freezable {
var isFrozen: Bool {
isValid == .smsValidationOTPTriggered || isValid == .smsValidationSuccessful
}
}
Now we must add validation for isFrozen value when a property is assigned. It can be added in property observers like:
...
public var phone: String? {
didSet {
validate()
}
}
...
private func validate() {
assert(!isFrozen)
}
Or using property wrapper:
#propertyWrapper
struct Guarded<Value> {
private var value: Value
init(wrappedValue: Value) {
value = wrappedValue
}
#available(*, unavailable)
var wrappedValue: Value {
get { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
set { fatalError("only works on instance properties of classes that conforms to Freezable protocol") }
}
static subscript<EnclosingSelf: Freezable>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Value>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Self>
) -> Value {
get {
object[keyPath: storageKeyPath].value
}
set {
precondition(!object.isFrozen, "Object \(object) is frozen! Modifications are forbidden")
object[keyPath: storageKeyPath].value = newValue
}
}
}
So your class will look like:
class PhonesViewModel: NSCopying {
#Guarded
public var phoneType: PhoneNumberType = PhoneNumberType.mobile
#Guarded
public var phone: String?
#Guarded
public var code: String?
#Guarded
public var countryCode: String?
#Guarded
public var isValid : PhoneValidation?
func copy(with zone: NSZone? = nil) -> Any {
let copy = PhonesViewModel()
copy.phoneType = phoneType
copy.phone = phone
copy.code = code
copy.countryCode = countryCode
copy.isValid = isValid
return copy
}
}

Swift: Filter nested arrays

I'm trying to filter an array of Deals with a status DealStaus which has a nested array of Bookings, each one with a BookingStatus.
I want to filter Deals with status .won and Bookings according to the statuses given when calling the function. BookingStatus and DealStatus are both enums.
Deal and Booking look like this:
public struct Deal: Decodable {
public let identifier: String?
public let status: DealStatus
public let bookings: [Booking]?
}
public struct Booking: Decodable {
public let identifier: String?
public let status: BookingStatus
public let startDate: Date?
public let endDate: Date?
}
To do so I wrote the following snippet:
private func getDeals(with bookingStatus: [BookingStatus]) -> [Deal] {
guard let user = currentUser, let deals = user.deals else { return [Deal]() } // Note: user is a class attribute
return deals.filter { $0.status == .won && $0.bookings?.filter { bookingStatus.contains($0.status) }}
}
This does not work. The compiler gives the following error:
Optional type '[Booking]?' cannot be used as a boolean; test for '!=
nil' instead
Following the instructions of #matt, I broke it down:
private func getDeals(with bookingStatus: [BookingStatus]) -> [Deal] {
guard let user = currentUser, let deals = user.deals else { return [Deal]() }
return deals
.filter { $0.status == .won }
.filter { $0.bookings?.contains(where: { bookingStatus.contains($0.status)} ) ?? false }
}

Instantiating classes stored in metatype Dictionary

I've followed the solution at Make a Swift dictionary where the key is "Type"? to create dictionaries that can use a class type as keys.
What I want to do is: I have one dictionary that should store class types with their class type (aka metatype) as keys, too:
class MyScenario {
static var metatype:Metatype<MyScenario> {
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
Then I have methods to register and execute scenarios:
public func registerScenario(scenarioID:MyScenario.Type) {
if (scenarioClasses[scenarioID.metatype] == nil) {
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type) {
if let scenarioClass = scenarioClasses[scenarioID.metatype] {
let scenario = scenarioClass()
}
}
... Problem is in the last line:
Constructing an object of class type 'MyScenario' with a metatype
value must use a 'required' initializer.
It looks like the compiler is confused at that point since I cannot use 'required' at that assignment. Does anyone have an idea how I would have to instantiate the scenarioClass in executeScenario()?
This must do the job.
import Foundation
struct Metatype<T> : Hashable
{
static func ==(lhs: Metatype, rhs: Metatype) -> Bool
{
return lhs.base == rhs.base
}
let base: T.Type
init(_ base: T.Type)
{
self.base = base
}
var hashValue: Int
{
return ObjectIdentifier(base).hashValue
}
}
public class MyScenario
{
var p: String
public required init()
{
self.p = "any"
}
static var metatype:Metatype<MyScenario>
{
return Metatype(self)
}
}
var scenarioClasses:[Metatype<MyScenario>: MyScenario.Type] = [:]
public func registerScenario(scenarioID:MyScenario.Type)
{
if (scenarioClasses[scenarioID.metatype] == nil)
{
scenarioClasses[scenarioID.metatype] = scenarioID
}
}
public func executeScenario(scenarioID:MyScenario.Type)
{
if let scenarioClass = scenarioClasses[scenarioID.metatype]
{
let scenario = scenarioClass.init()
print("\(scenario.p)")
}
}
// Register a new scenario
registerScenario(scenarioID: MyScenario.self)
// Execute
executeScenario(scenarioID: MyScenario.self)
// Should print "any"

Unique Objects inside a Array Swift

I have an array, with custom objects.
I Would like to pop the repeated objects, with the repeated properties:
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
in this case, pop the product2 or product3
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
using this you can so this
let unique = [product,product2,product3].unique{$0.subCategory}
this has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination
You can use Swift Set:
let array = [product,product2,product3]
let set = Set(array)
You have to make Product conform to Hashable (and thus, Equatable) though:
class Product : Hashable {
var subCategory = ""
var hashValue: Int { return subCategory.hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.subCategory == rhs.subCategory
}
And, if Product was a NSObject subclass, you have to override isEqual:
override func isEqual(object: AnyObject?) -> Bool {
if let product = object as? Product {
return product == self
} else {
return false
}
}
Clearly, modify those to reflect other properties you might have in your class. For example:
class Product : Hashable {
var category = ""
var subCategory = ""
var hashValue: Int { return [category, subCategory].hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}
If Product conforms to Equatable, where a product is equal based on it's subcategory (and you don't care about order), you can add the objects to a set, and take an array from that set:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects
or
let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)
If your class conforms to protocol Hashable and you would like to keep the original array order you can create an extension as follow:
extension Array where Element: Hashable {
var uniqueElements: [Element] {
var elements: [Element] = []
for element in self {
if let _ = elements.indexOf(element) {
print("item found")
} else {
print("item not found, add it")
elements.append(element)
}
}
return elements
}
}
class Product {
var subCategory: String = ""
}
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
extension Product : Hashable {
var hashValue: Int {
return subCategory.hashValue
}
}
func ==(lhs: Product, rhs: Product)->Bool {
return lhs.subCategory == rhs.subCategory
}
let set = Set(array)
set.forEach { (p) -> () in
print(p, p.subCategory)
}
/*
Product one
Product two
*/
if an item is part of set or not doesn't depends on hashValue, it depends on comparation. if your product conform to Hashable, it should conform to Equatable. if you need that the creation of the set depends solely on subCategory, the comparation should depends solely on subCategory. this can be a big trouble, if you need to compare your products some other way
Here is a KeyPath based version of the Ciprian Rarau' solution
extension Array {
func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
var set = Set<T>()
return self.reduce(into: [Element]()) { result, value in
guard !set.contains(value[keyPath: keyPath]) else {
return
}
set.insert(value[keyPath: keyPath])
result.append(value)
}
}
}
example usage:
let unique = [product, product2, product3].unique(by: \.subCategory)

swift - Can I compare Any.Type?

I defined two types as follow: I want to define a function 'matches' that compares two KeyTypePairs and returns true or false depends on matching on key and type.
protocol KeyTypePair: Hashable {
typealias val
var key: String { get }
var type: Any.Type { get }
}
public struct KeyTypePairOf<T>: KeyTypePair {
typealias val = T
let _key: String
let _type: Any.Type
public var key: String {
get {
return _key
}
}
public var type: Any.Type {
get {
return _type
}
}
public var hashValue: Int {
get {
return _key.hashValue
}
}
public init(key: String) {
self._key = key
self._type = T.self
}
init<T: KeyTypePair>(property: T) {
self._key = pair.key
self._type = pair.type
}
func matches<T: KeyTypePair>(pair:T) -> Bool {
let x = self._type == pair.type // invalid, how do I check equal types?
return self._key == pair.key && x
}
}
How do I compare the 'types' of the struct? Been a headache. Should I use AnyObject instead?
I tested this in a Swift 4 playground
struct Foo {}
struct Bar {}
let fooType: Any.Type = Foo.self
let barType: Any.Type = Bar.self
fooType == Foo.self // true
fooType is Foo.Type // true
fooType == barType // false
The tool you want is object_getClassName (which returns a String). Right now, you can't directly compare types. This feels like just a missing compiler feature rather than anything deep about swift. You'd think you could compare x.dynamicType == y.dynamicType, but that doesn't currently work. See also What is the pattern for entities and Equatable?

Resources