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 }
Related
Here's the deal,
I'm writing an SDK, and I want to declare observers as protocols, instead of classes or structs (It's sort of an "Observer/Delegate" hybrid).
I want to be able to compare two arguments that are passed in as protocol references, as opposed to the concrete classes/structs they actually are, IRL.
I know that the "easy" way to get comparison is to constrain the protocols to Hashable or Equatable, but I want to avoid burdening the user (It's an SDK).
Here's a little playground with what I mean:
protocol A {
func AFunc() -> String
}
class APrime: A {
func AFunc() -> String { "I AM GROOT" }
}
let variableA = APrime()
let variableB = APrime()
func compareTypes(_ inA: A, _ inB: A) -> String {
// if inA == inB {
// return ""
// }
return "not "
}
print("A is \(compareTypes(variableA, variableB))B.")
print("A is \(compareTypes(variableA, variableA))A.")
The raunchy bit is the commented-out section in compareTypes(_: A, _: A). I need to figure out how to compare them without going into "Hacksylvania," which I could do by doing something like comparing addresses of the AFunc() in each instance.
The expected output is:
A is not B.
A is A.
Any ideas for a more "swifty" approach? I must be missing the forest for the trees.
Just to add some closure to this, here is how I solve this:
protocol A {
var uuid: Int { get } // This is the secret sauce. It will contain a unique UUID, associated with the instance.
func AFunc() -> String
}
class APrime: A {
let uuid: Int = Int.random(in: 0..<1000) // The UUID is initialized with the instance.
func AFunc() -> String { "I AM GROOT" }
}
let variableA = APrime()
let variableB = APrime()
let variableC = variableA
func compareTypes(_ inA: A, _ inB: A) -> String {
if inA.uuid == inB.uuid { // We compare UUIDs.
return ""
}
return "not "
}
print("C is \(compareTypes(variableC, variableB))B.")
print("C is \(compareTypes(variableC, variableA))A.")
The "uuid" variable is usually an actual UUID type, but I didn't want to import Foundation in the example, so I just did a simple rand. It gets the point across.
This outputs:
C is not B.
C is A.
And there is another way (that I also use, sometimes):
protocol B {
func BFunc() -> String
func amIThisOne(_ instanceToCompare: B) -> Bool // This is an identity comparator
}
class BPrime: B {
func BFunc() -> String { "I AM GROOT'S BROTHER" }
// We compare ourselves against the other instance, assuming it can be cast to our own type.
func amIThisOne(_ inInstanceToCompare: B) -> Bool {
guard let instanceToCompare = inInstanceToCompare as? Self else { return false }
return self === instanceToCompare
}
}
let variableD = BPrime()
let variableE = BPrime()
let variableF = variableD
print("D is \(variableE.amIThisOne(variableD) ? "" : "not ")E.")
print("D is \(variableD.amIThisOne(variableF) ? "" : "not ")F.")
Which outputs:
D is not E.
D is F.
This allows a more programmatic way of comparing the instances.
HOW NOT TO DO IT
And then, of course, if we have control of the instances, we can truly do the Equatable thing (This requires that the playground import Foundation):
protocol C: Equatable {
func CFunc() -> String
}
class CPrime: C {
// This is actually not what I want, as I want to compare protocols, not conforming classes.
static func == (lhs: CPrime, rhs: CPrime) -> Bool {
guard let lhs = lhs as? Self else { return false }
guard let rhs = rhs as? Self else { return false }
return lhs === rhs
}
func CFunc() -> String { "I AM GROOT'S UDDER BROTHER" }
}
let variableG = CPrime()
let variableH = CPrime()
let variableI = variableG
print("G is \(variableG == variableH ? "" : "not ")H.")
print("G is \(variableI == variableG ? "" : "not ")I.")
Which outputs:
G is not H.
G is I.
This is my code (simplified code):
struct SomeStruct {
let id: Int
let age: Int
}
extension SomeStruct: Hashable {
var hashValue: Int {
return id.hashValue * age.hashValue
}
static func ==(lhs: SomeStruct, rhs: SomeStruct) -> Bool {
return lhs.id == rhs.id && lhs.age == rhs.age
}
}
struct Calculator {
let struct1: [SomeStruct]
let struct2: [SomeStruct]
func uniqueById() {
let struct3 = Set(struct2).union(Set(struct1))
// I want to union it by property 'id' only.
// If the property 'id' is equal for both objects,
// the object in struct2 should be used (since that can have a different age property)
}
}
SomeStruct is a generated struct which I do not want to edit. I want to create a Set for SomeStruct that is based on 1 property: id. For that, I think I need a custom Comparator, just as Java has. Is there any Swifty way? This is the only thing I can come up with, but I am wondering if there is a better way:
struct SomeStructComparatorById: Hashable {
let someStruct: SomeStruct
var hashValue: Int {
return someStruct.id.hashValue
}
static func ==(lhs: SomeStructComparatorById, rhs: SomeStructComparatorById) -> Bool {
return lhs.someStruct.id == rhs.someStruct.id
}
}
First, I don't think this would work in Java. addAll() doesn't take a Comparator (nor does contains, etc.) Comparators are for sorting, not equality. Conceptually this is breaking how Set works in any language. Two items are not "equal" unless they can be swapped in all cases.
That tells us that we don't want a Set here. What you want here is uniqueness based on some key. That's a Dictionary (as Daniel discusses).
You could either just have a "id -> age" dictionary or "id -> struct-of-other-properties" dictionary as your primary data type (rather than using Array). Or you can turn your Array into a temporary Dictionary like this:
extension Dictionary {
init<S>(_ values: S, uniquelyKeyedBy keyPath: KeyPath<S.Element, Key>)
where S : Sequence, S.Element == Value {
let keys = values.map { $0[keyPath: keyPath] }
self.init(uniqueKeysWithValues: zip(keys, values))
}
}
And merge them like this:
let dict1 = Dictionary(struct1, uniquelyKeyedBy: \.id)
let dict2 = Dictionary(struct2, uniquelyKeyedBy: \.id)
let merged = dict1.merging(dict2, uniquingKeysWith: { old, new in old }).values
This leaves merged as [SomeStruct].
Note that this Dictionary(uniquelyKeyedBy:) has the same preconditions as Dictionary(uniqueKeysWithValues:). If there are duplicate keys, it's a programming error and will raise precondition failure.
You could do something like this:
var setOfIds: Set<Int> = []
var struct3 = struct2.filter { setOfIds.insert($0.id).inserted }
struct3 += struct1.filter { setOfIds.insert($0.id).inserted }
The result would be an array of SomeStruct, with all elements with unique ids.
You could define this as a custom operator :
infix operator *>
func *> (lhs: [SomeStruct], rhs: [SomeStruct]) -> [SomeStruct] {
var setOfIds: Set<Int> = []
var union = lhs.filter { setOfIds.insert($0.id).inserted }
union += rhs.filter { setOfIds.insert($0.id).inserted }
return union
}
Your code would then look like this:
func uniqueById() {
let struct3 = struct2 *> struct1
//use struct3
}
The short answer is no. Swift sets do not have any way to accept a custom comparator and if you absolutely must have a Set, then your wrapper idea is the only way to do it. I question the requirement for a set though.
Instead of using Set in your calculator, I recommend using dictionary.
You can use a Dictionary to produce an array where each item has a unique ID...
let struct3 = Dictionary(grouping: struct1 + struct2, by: { $0.id })
.compactMap { $0.value.max(by: { $0.age < $1.age })}
Or you can keep the elements in a [Int: SomeStruct] dictionary:
let keysAndValues = (struct1 + struct2).map { ($0.id, $0) }
let dictionary = Dictionary(keysAndValues, uniquingKeysWith: { lhs, rhs in
lhs.age > rhs.age ? lhs : rhs
})
I have an array of objects and i would like to order it. Thats normally not a huge problem, but in my special case i would like to order string values like:
CAT-1, CAT-2, ....CAT-10
Where now CAT-10 is before CAT-2.
I already found that i can sort string values in this case with:
let items = items.sorted {
(s1, s2) -> Bool in return s1.localizedStandardCompare(s2) == .orderedAscending
}
But this is only working when the the array contains only string values. In my case i would like to sort it with something like this:
self.filteredItems.sort{
return $0.position < $1.position
}
But i am not sure how can i combine the function above to solve that special sort order?
Please check:
class Category {
let name: String
init(_ name: String) {
self.name = name
}
}
var catA = Category("CAT-2")
var catB = Category("CAT-10")
var catC = Category("CAT-1")
let items: [Category] = [catA, catB, catC]
let filteredItems: [Category] = items.sorted { (s1, s2) -> Bool in
return s1.name.localizedStandardCompare(s2.name) == .orderedAscending
}
print(filteredItems[0].name, filteredItems[1].name, filteredItems[2].name)
// Output : CAT-1 CAT-2 CAT-10
let sortedBy = items.sorted { (c1, c2) -> Bool in
return c1.name < c2.name
}
print(sortedBy[0].name, sortedBy[1].name, sortedBy[2].name)
// Output : CAT-1 CAT-10 CAT-2
** 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.
I'm trying to sort the array that is being set before setting it but the argument of willSet is immutable and sort mutates the value. How can I overcome this limit?
var files:[File]! = [File]() {
willSet(newFiles) {
newFiles.sort { (a:File, b:File) -> Bool in
return a.created_at > b.created_at
}
}
}
To put this question out of my own project context, I made this gist:
class Person {
var name:String!
var age:Int!
init(name:String, age:Int) {
self.name = name
self.age = age
}
}
let scott = Person(name: "Scott", age: 28)
let will = Person(name: "Will", age: 27)
let john = Person(name: "John", age: 32)
let noah = Person(name: "Noah", age: 15)
var sample = [scott,will,john,noah]
var people:[Person] = [Person]() {
willSet(newPeople) {
newPeople.sort({ (a:Person, b:Person) -> Bool in
return a.age > b.age
})
}
}
people = sample
people[0]
I get the error stating that newPeople is not mutable and sort is trying to mutate it.
It's not possible to mutate the value inside willSet. If you implement a willSet observer, it is passed the new property value as a constant parameter.
What about modifying it to use didSet?
var people:[Person] = [Person]()
{
didSet
{
people.sort({ (a:Person, b:Person) -> Bool in
return a.age > b.age
})
}
}
willSet is called just before the value is stored.
didSet is called immediately after the new value is stored.
You can read more about property observers here
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Properties.html
You can also write a custom getter and setter like below. But didSet seems more convenient.
var _people = [Person]()
var people: [Person] {
get {
return _people
}
set(newPeople) {
_people = newPeople.sorted({ (a:Person, b:Person) -> Bool in
return a.age > b.age
})
}
}
It is not possible to change value types (including arrays) before they are set inside of willSet. You will need to instead use a computed property and backing storage like so:
var _people = [Person]()
var people: [Person] {
get {
return _people
}
set(newPeople) {
_people = newPeople.sorted { $0.age > $1.age }
}
}
Another solution for people who like abstracting away behavior like this (especially those who are used to features like C#'s custom attributes) is to use a Property Wrapper, available since Swift 5.1 (Xcode 11.0).
First, create a new property wrapper struct that can sort Comparable elements:
#propertyWrapper
public struct Sorting<V : MutableCollection & RandomAccessCollection>
where V.Element : Comparable
{
var value: V
public init(wrappedValue: V) {
value = wrappedValue
value.sort()
}
public var wrappedValue: V {
get { value }
set {
value = newValue
value.sort()
}
}
}
and then assuming you implement Comparable-conformance for Person:
extension Person : Comparable {
static func < (lhs: Person, rhs: Person) -> Bool {
lhs.age < lhs.age
}
static func == (lhs: Person, rhs: Person) -> Bool {
lhs.age == lhs.age
}
}
you can declare your property like this and it will be auto-sorted on init or set:
struct SomeStructOrClass
{
#Sorting var people: [Person]
}
// … (given `someStructOrClass` is an instance of `SomeStructOrClass`)
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people.last
Caveat: Property wrappers are not allowed (as of time of writing, Swift 5.7.1) in top-level code— they need to be applied to a property var in a struct, class, or enum.
To more literally follow your sample code, you could easily also create a ReverseSorting property wrapper:
#propertyWrapper
public struct ReverseSorting<V : MutableCollection & RandomAccessCollection & BidirectionalCollection>
where V.Element : Comparable
{
// Implementation is almost the same, except you'll want to also call `value.reverse()`:
// value = …
// value.sort()
// value.reverse()
}
and then the oldest person will be at the first element:
// …
#Sorting var people: [Person]
// …
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people[0]
And even more directly, if your use-case demands using a comparison closure via sort(by:…) instead of implementing Comparable conformance, you can do that to:
#propertyWrapper
public struct SortingBy<V : MutableCollection & RandomAccessCollection>
{
var value: V
private var _areInIncreasingOrder: (V.Element, V.Element) -> Bool
public init(wrappedValue: V, by areInIncreasingOrder: #escaping (V.Element, V.Element) -> Bool) {
_areInIncreasingOrder = areInIncreasingOrder
value = wrappedValue
value.sort(by: _areInIncreasingOrder)
}
public var wrappedValue: V {
get { value }
set {
value = newValue
value.sort(by: _areInIncreasingOrder)
}
}
}
// …
#SortingBy(by: { a, b in a.age > b.age }) var people: [Person] = []
// …
someStructOrClass.people = sample
let oldestPerson = someStructOrClass.people[0]
Caveat: The way SortingBy's init currently works, you'll need to specify an initial value ([]). You can remove this requirement with an additional init (see Swift docs), but that approach is much less complicated when your property wrapper works on a concrete type (e.g. if you wrote a non-generic PersonArraySortingBy property wrapper), as opposed to a generic-on-protocols property wrapper.