Equatable not detecting the difference between two objects of the same class - swift

I have an item of the class Product. I'm changing a variable within the Product class but my addToCart method below treats the item as if no changes have been made. I'm comparing the products based on the id and the variationId. What am I doing wrong?
import UIKit
class Product: Equatable {
let id: Int
let name: String
var variationId: Int
var quantity: Int
init(id: Int, name: String, variationId: Int, quantity: Int) {
self.id = id
self.name = name
self.variationId = variationId
self.quantity = quantity
}
static func == (lhs: Product, rhs: Product) -> Bool {
return
lhs.id == rhs.id && lhs.variationId == rhs.variationId
}
}
The user can select a different color for the product and in doing so changes the variationId.
The addItemToCart() method checks if the cartItems array contains this product. If the product exists, the quantity gets increased by 1 otherwise the product is added to the array.
var cartItems = [Product]()
func addItemToCart(product: Product) {
if cartItems.contains(product) {
let quantity = product.quantity
product.quantity = quantity + 1
} else {
cartItems.append(product)
}
}
The method above keeps updating the quantity regardless if the variationId is different or not.

You are not updating the correct object. Your addItemToCart(product:) function should be something like this:
func addItemToCart(product: Product) {
if let cartItemIndex = cartItems.firstIndex(of: product) {
cartItems[cartItemIndex].quantity += product.quantity
} else {
cartItems.append(product)
}
}

You can do this as follows, you can also remove the equatable attribute.
var cartItems = [Product]()
func addItemToCart(product: Product) {
if let cardItemIndex = cardItems.firstIndex(where: { $0.id == product.id && $0.variationId == product.variationId}) {
cartItems[cardItemIndex].quantity += 1
} else {
cardItems.append(product)
}
}

Related

Sort array in swiftui by multiple conditions

how it is possible to sort an array by multiple conditions.
struct UserInformationModel: Identifiable, Hashable {
let id = UUID()
var isVip: Bool
let userIsMale: Bool
let userName: String
let age: Int
let userCountry: String
let countryIsoCode: String
let uid: String
}
And the view model contain the code:
class GetUserInformationViewModel: ObservableObject {
#Published var allUsers = [UserInformationModel]()
fun sortmyarray(){
self.allUsers = self.allUsers.sorted(by: {$0.isVip && !$1.isVip})
}
how its possible to sort first the vip users, and then sort by age and then country?
Here is a simple way to sort on multiple properties (I have assumed a sort order here for each property since it isn't mentioned in the question)
let sorted = users.sorted {
if $0.isVip == $1.isVip {
if $0.age == $1.age {
return $0.userCountry < $1.userCountry
} else {
return $0.age < $1.age
}
}
return $0.isVip && !$1.isVip
}
If the above is the natural sort order for the type then you could let the type implement Comparable and implement the < func
struct UserInformationModel: Identifiable, Hashable, Comparable {
//properties removed for brevity
static func < (lhs: UserInformationModel, rhs: UserInformationModel) -> Bool {
if lhs.isVip == rhs.isVip {
if lhs.age == rhs.age {
return lhs.userCountry < rhs.userCountry
} else {
return lhs.age < rhs.age
}
}
return lhs.isVip && !rhs.isVip
}
}
and then the sorting would be
let sorted = users.sorted()
Use tuples.
allUsers.sorted {
($1.isVip.comparable, $0.age, $0.userCountry)
<
($0.isVip.comparable, $1.age, $1.userCountry)
}
public extension Bool {
/// A way to compare `Bool`s.
///
/// Note: `false` is "less than" `true`.
enum Comparable: CaseIterable, Swift.Comparable {
case `false`, `true`
}
/// Make a `Bool` `Comparable`, with `false` being "less than" `true`.
var comparable: Comparable { .init(booleanLiteral: self) }
}
extension Bool.Comparable: ExpressibleByBooleanLiteral {
public init(booleanLiteral value: Bool) {
self = value ? .true : .false
}
}

Swift functional way to get single item from a subarray

I have the following function (example), which I would like to write in a functional way:
func passengerForId(_ id: Int) -> Passenger?
{
for car in cars
{
for passenger in car.passengers
{
if passenger.id == id
{
return passenger
}
}
}
return nil
}
What would be the best way?
I've tried the following:
func passengerForId(_ id: Int) -> Passenger?
{
return cars.first(where: { $0.passengers.contains(where: { $0.id == id } ) })
}
However, that obviously returns the car containing the requested passenger, and not the passenger itself.
Another option would something like:
func passengerForId(_ id: Int) -> Passenger?
{
let passengers = cars.flatMap { car in car.passengers }
return passengers.first(where: { $0.id == id })
}
or
func passengerForId(_ id: Int) -> Passenger?
{
return cars.flatMap { car in car.passsengers.filter { $0.id == id }}.first
}
However, that seems less efficient than the original, as it will loop over all the areas.
Is there a better way to do this?
You can create a (lazy) flattened collection of all passengers, and pick
the first matching one:
func passengerForId(_ id: Int) -> Passenger? {
return cars.lazy.flatMap({ $0.passengers }).first(where: { $0.id == id } )
}

Swift 3 Equatable Struct optional function

This will be a little long winded so please bear with me. I am also a bit of a swift beginner as well. I have an array with a defined struct.
var modelOriginalArray = [model]()
struct model: Equatable {
var modelID = String()
var modelName = String()
var modelNumber = String()
var manufacturer = String()
var manufShort = String()
var phiTypeCode = String()
var phiTypeDesc = String()
init(modelID: String, modelName: String, modelNumber: String, manufacturer: String, manufShort: String, phiTypeCode: String, phiTypeDesc: String) {
self.modelID = modelID
self.modelName = modelName
self.modelNumber = modelNumber
self.manufacturer = manufacturer
self.manufShort = manufShort
self.phiTypeCode = phiTypeCode
self.phiTypeDesc = phiTypeDesc
}
static func == (lhs: model, rhs: model) -> Bool {
return lhs.manufShort == rhs.manufShort && lhs.modelName == rhs.modelName && lhs.modelNumber == rhs.modelNumber
}
}
I load about 5000 records into this array. I then have a need to filter this array based on search criteria. Let's say I am looking for a manufacturer "Sony". There is the possibility of multiple models for Sony so I need to separate all Sony records from the greater 5000.
srchval = "SONY"
var filteredArray = [model]()
var uniqueFilteredArray = [model]()
filteredArray = self.modelOriginalArray.filter { $0.manufShort.range(of: srchval, options: .caseInsensitive) != nil }
This will give me an array with only "Sony" records. However there is a possibility that some of those "Sony" records have duplicate manufShort, modelName, modelNumber values under different modelID's. I need to separate those and only have unique records.
// Find Uniquic options by removing duplicate Model Names
uniqueFilteredArray = unique(models: filteredArray)
func unique(models: [model]) -> [model] {
var uniqueModels = [model]()
for model in models {
if !uniqueModels.contains(model) {
uniqueModels.append(model)
}
}
return uniqueModels
}
This all works ver well. The problem I have is in the filter there are situations where I have to make sure the record is matching on:
static func == (lhs: model, rhs: model) -> Bool {
return lhs.manufShort == rhs.manufShort && lhs.modelName == rhs.modelName && lhs.modelNumber == rhs.modelNumber
}
And in a different situation in the same class I need to match only on the manufShort:
static func == (lhs: model2, rhs: model2) -> Bool {
return lhs.manufShort == rhs.manufShort
}
I have tried creating a separate model i.e.. model2 with this different static function, but I have difficulties moving data from one array to another with a different struct.
Any thoughts or a better way to accomplish this?
Thanks
Since you use two different approaches for defining the "equality" of two models, you should probably consider not using the == operator, as you don't really test equality if the equality predicate is different from case to case. Rather, you have two different custom predicates (which applies to two model instances) that you would like to use in different contexts. Why not use two custom type (static) methods for this, with descriptive names, semantically describing their different meanings?
Thanks! That makes a great deal of sense, how do I call the different (static) methods so that it will hit the right function based on what I am after. An example?
Example setup:
struct Foo {
let id: String
let bar: Int
let baz: Int
let bax: Int
init(_ id: String, _ bar: Int, _ baz: Int, _ bax: Int)
{
self.id = id
self.bar = bar
self.baz = baz
self.bax = bax
}
static func byBarEqualityPredicate(lhs: Foo, rhs: Foo) -> Bool {
return lhs.bar == rhs.bar
}
static func byBazAndBaxEqualityPredicate(lhs: Foo, rhs: Foo) -> Bool {
return lhs.baz == rhs.baz && lhs.bax == rhs.bax
}
}
let fooArr = [Foo("Foo A", 1, 2, 3),
Foo("Foo B", 1, 1, 2),
Foo("Foo C", 3, 1, 2)]
A slightly modified version of your unique method, now supplying a (Foo, Foo) -> Bool predicate among with the foos array:
func unique(foos: [Foo], predicate: (Foo, Foo) -> Bool) -> [Foo] {
var uniqueFoos = [Foo]()
for foo in foos {
if !uniqueFoos.contains(where: { predicate($0, foo) }) {
uniqueFoos.append(foo)
}
}
return uniqueFoos
}
Testing with the two different Foo predicates:
// by 'bar' "equality": Foo A and Foo B will be considered "equal",
// and only Foo A, among these two , will be added to the "unique" array.
let fooWithBarPredicate = unique(foos: fooArr, predicate: Foo.byBarEqualityPredicate)
fooWithBarPredicate.forEach { print($0.id) } // Foo A, Foo C
// by 'baz' && 'bax' "equality": Foo A and Foo C will be considered "equal",
// and only Foo A, among these two, will be added to the "unique" array.
let fooWithBazBaxPredicate = unique(foos: fooArr, predicate: Foo.byBazAndBaxEqualityPredicate)
fooWithBazBaxPredicate.forEach { print($0.id) } // Foo A, Foo B
You can use the following extension on Collection. Not tested.
extension Collection where Iterator.Element: Equatable {
func uniques(by equals: (Iterator.Element, Iterator.Element) -> Bool) -> [Iterator.Element] {
var uniqueElems: [Iterator.Element] = []
for elem in self {
if uniqueElems.index(where: { equals($0, elem) }) == nil {
uniqueElems.append(elem)
}
}
return uniqueElems
}
}
Then you can use
filteredArray.uniques { $0.manufShort == $1.manufShort }
filteredArray.uniques { $0.manufShort == $1.manufShort && $0.modelName == $1.modelName && $0.modelNumber == $1.modelNumber }

Hashing problems using a wrapper class around NSUUID as the key

** REWRITE **
OK, it turns out I'm really asking a different question. I understand about hashValue and ==, so that's not relevant.
I would like my wrapper class BUUID to "do the right thing" and act just like NSUUID's act in a Dictionary.
See below, where they don't.
import Foundation
class BUUID: NSObject {
init?(str: String) {
if let uuid = NSUUID(UUIDString: str) {
_realUUID = uuid
}
else {
return nil
}
}
override init() {
_realUUID = NSUUID()
}
private var _realUUID: NSUUID
override var description: String { get { return _realUUID.UUIDString } }
override var hashValue: Int { get { return _realUUID.hashValue } }
var UUIDString: String { get { print("WARNING Use description or .str instead"); return _realUUID.UUIDString } }
var str: String { get { return _realUUID.UUIDString } }
}
func ==(lhs: BUUID, rhs: BUUID) -> Bool { return lhs._realUUID == rhs._realUUID }
let a = BUUID()
let b = BUUID(str: a.str)!
print("a: \(a)\nb: \(b)")
print("a === b: \(a === b)")
print("a == b: \(a == b)")
var d = [a: "Hi"]
print("\(d[a]) \(d[b])")
let nA = NSUUID()
let nB = NSUUID(UUIDString: nA.UUIDString)!
print("na: \(nA)\nnB: \(nB)")
print("nA === nB: \(nA === nB)")
print("nA == nB: \(nA == nB)")
var nD = [nA: "Hi"]
print("\(nD[nA]) \(nD[nB])")
Results. Note that I can look up using NSUUID nB and get back what I put under nA. Not so with my BUUID.
a: 9DE6FE91-D4B5-4A6B-B912-5AAF34DB41C8
b: 9DE6FE91-D4B5-4A6B-B912-5AAF34DB41C8
a === b: false
a == b: true
Optional("Hi") nil
nA: <__NSConcreteUUID 0x7fa193c39500> BB9F9851-93CF-4263-B98A-5015810E4286
nB: <__NSConcreteUUID 0x7fa193c37dd0> BB9F9851-93CF-4263-B98A-5015810E4286
nA === nB: false
nA == nB: true
Optional("Hi") Optional("Hi")
Inheriting from NSObject also assumes isEqual(object: AnyObject?) -> Bool method overloading:
import Foundation
class BUUID: NSObject {
init?(str: String) {
if let uuid = NSUUID(UUIDString: str) {
_realUUID = uuid
}
else {
return nil
}
}
override init() {
_realUUID = NSUUID()
}
private var _realUUID: NSUUID
override func isEqual(object: AnyObject?) -> Bool {
guard let buuid = object as? BUUID else {
return false
}
return buuid._realUUID == _realUUID
}
override var description: String { get { return _realUUID.UUIDString } }
override var hashValue: Int { get { return _realUUID.hashValue } }
var UUIDString: String { get { print("WARNING Use description or .str instead"); return _realUUID.UUIDString } }
var str: String { get { return _realUUID.UUIDString } }
}
func ==(lhs: BUUID, rhs: BUUID) -> Bool { return lhs._realUUID == rhs._realUUID }
let a = BUUID()
let b = BUUID(str: a.str)!
print("a: \(a)\nb: \(b)")
print("a === b: \(a === b)")
print("a == b: \(a == b)")
var d = [a: "Hi"]
print("\(d[a]) \(d[b])")
let nA = NSUUID()
let nB = NSUUID(UUIDString: nA.UUIDString)!
print("na: \(nA)\nnB: \(nB)")
print("nA === nB: \(nA === nB)")
print("nA == nB: \(nA == nB)")
var nD = [nA: "Hi"]
print("\(nD[nA]) \(nD[nB])")
So the answer is to not make BUUID inherit from NSObject, which undercuts the Swiftiness of overriding ==.
So:
extension BUUID: Hashable {}
class BUUID: CustomStringConvertible {
// take away all 'override' keywords, nothing to override
// otherwise same as above
}
Interesting!
This answer is relevant to initially asked question: Why that's possible to get two key-value pairs with identical key's hashes in a dictionary
This example illustrates that keys in Dictionary can have identical hashes, but equality operation should return false for different keys:
func ==(lhs: FooKey, rhs: FooKey) -> Bool {
return unsafeAddressOf(lhs) == unsafeAddressOf(rhs)
}
class FooKey: Hashable, Equatable {
var hashValue: Int {
get {
return 123
}
}
}
var d = Dictionary<FooKey, String>()
let key1 = FooKey()
let key2 = FooKey()
d[key1] = "value1"
d[key2] = "value2"
Output
[FooKey: "value1", FooKey: "value2"]
That's definitely not good to have all keys with the same hash. In this case we are getting that worst case when search element complexity fells down to O(n) (exhaustive search). But it will work.

Swift: Returning a Boolean value

I am creating a sort of tilling system that will take into account offers on certain products. I need to create a half price offer that will display either true or false depending whether the offer has been applied.
HalfPrice Class:
class HalfPriceOffer :Offer {
init(){
super.init(name: "Half Price on Wine")
applicableProducts = [901,902];
}
override func isApplicableToList(list: [ShoppingItem]) -> Bool {
//should return true if a dicount can be applied based on the supplied list of products (i.e. a product must be matched to an offer)
return false
}
ShoppingItem Class
import Foundation
class ShoppingItem {
var name :String
var priceInPence :Int
var productId :Int
init(name:String, price:Int, productId:Int){
self.name = name
self.priceInPence = price
self.productId = productId
}
}
I know it uses loops; but I am unsure of how to actually write it. Thanks in advance :)
You could use the reduce function to achieve this:
func isApplicableToList(list: [ShoppingItem]) -> Bool {
return list.reduce(false) { (initial: Bool, current: ShoppingItem) -> Bool in
return initial || applicableProducts.contains(current.productId)
}
}
You can even write this shorter (Swift is awesome):
func isApplicableToList(list: [ShoppingItem]) -> Bool {
return list.reduce(false) { $0 || applicableProducts.contains($1.productId) }
}
It's perhaps less complex to think whether the offer items are in the list rather than the list items are in the offer.
override func isApplicableToList(list: [ShoppingItem]) -> Bool {
//should return true if a discount can be applied based on the supplied list of products (i.e. a product must be matched to an offer)
let a = list.map({$0.productId})
for p in applicableProducts
{
if !a.contains(p) {return false}
}
return true
}
And here's a full working code example, which fills in the implied gaps in the sample code:
class ShoppingItem {
var name: String
var priceInPence: Int
var productId: Int
init(name:String, price:Int, productId:Int){
self.name = name
self.priceInPence = price
self.productId = productId
}
}
class Offer {
var applicableProducts = [Int]()
var name:String
init(name: String) {
self.name = name
}
func isApplicableToList(list: [ShoppingItem]) -> Bool {
return false
}
}
class HalfPriceOffer: Offer {
init(){
super.init(name: "Half Price on Wine")
applicableProducts = [901,902]
}
override func isApplicableToList(list: [ShoppingItem]) -> Bool {
//should return true if a discount can be applied based on the supplied list of products (i.e. a product must be matched to an offer)
let a = list.map({$0.productId})
for p in applicableProducts
{
if !a.contains(p) {return false}
}
return true
}
}
let a = [ShoppingItem(name: "one", price: 1000, productId: 901), ShoppingItem(name: "two", price: 1009, productId: 907),ShoppingItem(name: "three", price: 1084, productId: 902)]
HalfPriceOffer().isApplicableToList(a) // true
let b = [ShoppingItem(name: "one", price: 1000, productId: 901), ShoppingItem(name: "two", price: 1009, productId: 907)]
HalfPriceOffer().isApplicableToList(b) // false