How do I create a Set of custom objects (Swift)? - ios

For my iOS app I have a model something like
class Person {
var Id: Int
var Name: String
init(id: Int, name: String?) {
self.Id = id
self.Name = name ?? ""
}
}
Then later on in my ViewController when I load data from the server I add some people to an array
class ViewController: UIViewController {
var people:[Person] = []
override func viewDidLoad() {
self.loadPeople()
}
func loadPeople() {
// This data will be coming from a server request
// so is just sample. It could have users which
// already exist in the people array
self.people.append(Person(id: "1", name: "Josh"))
self.people.append(Person(id: "2", name: "Ben"))
self.people.append(Person(id: "3", name: "Adam"))
}
}
What I am now trying todo is turn the people array into a Set<Person> so it will not add duplicates. Is this possible to do or do I need to change my logic?

To make set of Person you need to make it conform to Equatable and Hashable protocols:
class Person: Equatable, Hashable {
var Id: Int
var Name: String
init(id: Int, name: String?) {
self.Id = id
self.Name = name ?? ""
}
var hashValue: Int {
get {
return Id.hashValue << 15 + Name.hashValue
}
}
}
func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.Id == rhs.Id && lhs.Name == rhs.Name
}
Then you can use set of persons like this:
var set = Set<Person>()
set.insert(Person(id: 1, name: "name"))

With Swift 2.0, Hashable and Equitable is a part of NSObject. All you need to do is to override "isEqual" and "var hash:" for the property of interest. In this case: "Id", Set will exclude Person-objects with identical Ids.
class Person: NSObject {
var Id: Int
var Name: String
init(id: Int, name: String?) {
self.Id = id
self.Name = name ?? ""
}
override var hash: Int {
return Id.hashValue
}
override func isEqual(object: AnyObject?) -> Bool {
guard let rhs = object as? Person else {
return false
}
let lhs = self
return lhs.Id == rhs.Id
}
}
func mergeArrays(){
let person1 = Person(id: 1, name: "Tom")
let person2 = Person (id: 2, name: "John")
let person3 = Person(id: 3, name: "Adam")
let downloadedPeople = [person1,person2] //[{NSObject, Id 1, Name "Tom"}, {NSObject, Id 2, Name "John"}]
let peopleStoredLocally = [person1,person3] //[{NSObject, Id 1, Name "Tom"}, {NSObject, Id 3, Name "Adam"}]
let downloadedPeopleSet = Set(downloadedPeople) //{{NSObject, Id 2, Name "John"}, {NSObject, Id 1, Name "Tom"}}
let mergedSet = downloadedPeopleSet.union(peopleStoredLocally) //{{NSObject, Id 2, Name "John"}, {NSObject, Id 3, Name "Adam"}, {NSObject, Id 1, Name "Tom"}}
let mergedArray = Array(mergedSet)//[{NSObject, Id 2, Name "John"}, {NSObject, Id 3, Name "Adam"}, {NSObject, Id 1, Name "Tom"}]
}

UPDATE
Depretation warning when using hashValue:
'Hashable.hashValue' is deprecated as a protocol requirement; conform
type 'Person' to 'Hashable' by implementing 'hash(into:)' instead
Following the object Person example, nowadays implementation would be:
class Person: Equatable, Hashable {
let id: Int
let countryId: Int
var name: String
init(id: Int, countryId: Int, name: String) {
self.id = id
self.countryId = countryId
self.name = name
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(countryId)
}
static func == (lhs: Person, rhs: Person) -> Bool {
return lhs.id == rhs.id && lhs.countryId == rhs.countryId
}
}
Note: As per documentation the components used for hashing must be the same as the components compared in your type’s == operator implementation.

Related

Access element of the struct in array of structs

Hi I have a task to implement the Fleet protocol which has two functions:
addNewCar - adds a new car object to the Fleet.
- Parameter car: car to add to the Fleet
- Returns: false if the car with same id already exists in the Fleet, true – otherwise.
deleteCar - Deletes the car with the specified id from the Fleet.
- Returns: true if the car with same id existed in the Fleet, false – otherwise.
listCarsByModel - returns 10 car models containing the specified string.
If there are several cars with the same model, brand's name is added to car's model in the format "brand - car",
otherwise returns simply "car".
listCarsByBrand - returns 10 car models whose brand contains the specified string,
result is ordered by brand.
struct Car {
let id: String; // unique identifier
let model: String;
let brand: String;
}
protocol Fleet {
func addNewCar(car: Car) -> Bool
func deleteCar(id: String) -> Bool
func listCarsByModel(searchString: String) -> Set<String>
func listCarsByBrand(searchString: String) -> [String]
}
class FleetImpl: Fleet {
var cars: [Car] = []
func addNewCar(car: Car) -> Bool {
if let i = cars.firstIndex(where: { $0.id == car.id }) {
print(i)
return false
} else {
cars.append(car)
print(car)
return true
}
}
func deleteCar(id: String) -> Bool {
return true
}
func listCarsByModel(searchString: String) -> Set<String> {
}
func listCarsByBrand(searchString: String) -> [String] {
}
}
I've used method firstIndex(where:). But the function adds new car to the array with existing id. (i.e. two or more cars with the same id)
How can I access that 'id' property of Car struct in order to manipulate that data?
Please help me out
This is the test code:
func test(fleet: Fleet) {
assert(!fleet.deleteCar(id: "1"))
assert(fleet.addNewCar(car: Car(id: "1", model: "1", brand: "Lex")))
assert(!fleet.addNewCar(car: Car(id: "1", model: "any name because we check id only", brand: "any brand")))
assert(fleet.deleteCar(id: "1"))
assert(fleet.addNewCar(car: Car(id: "3", model: "Some Car3", brand: "Some Brand2")))
assert(fleet.addNewCar(car: Car(id: "4", model: "Some Car1", brand: "Some Brand3")))
var byModels: Set<String> = fleet.listCarsByModels(searchString: "Car")
assert(byModels.count == 10)
byModels = fleet.listCarsByModels(searchString: "Some Car")
assert(byModels.count == 4)
assert(byModels.contains("Some Brand3 - Some Car1"))
assert(byModels.contains("Some Car2"))
assert(byModels.contains("Some Car3"))
assert(!byModels.contains("Some Car1"))
assert(byModels.contains("Some Brand1 - Some Car1"))
var byBrand: [String] = fleet.listCarsByBrand(searchString: "Brand")
assert(byBrand.count == 10)
byBrand = fleet.listCarsByBrand(searchString: "Some Brand")
assert(byBrand.count == 4)
assert(byBrand[0] == "Some Car1")
assert(byBrand[1] == "Some Car2" || byBrand[1] == "Some Car3")
assert(byBrand[2] == "Some Car2" || byBrand[2] == "Some Car3")
assert(byBrand[3] == "Some Car1")
}
test(fleet: FleetImpl())
Maybe a set instead of an array would work better in this case as we're dealing with unique elements. Also note that rather than using firstIndex(where) I'm using first(where) which will return the car rather than index.
import UIKit
struct Car: Hashable {
let id: String
let model: String
let brand: String
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
protocol Fleet {
func addNewCar(car: Car) -> Bool
func deleteCar(id: String) -> Bool
func listCarsByModel(searchString: String) -> [String]
func listCarsByBrand(searchString: String) -> [String]
}
class FleetImpl: Fleet {
var cars = Set<Car>()
func addNewCar(car: Car) -> Bool {
guard !cars.contains(where: { $0.id == car.id }) else { return false }
cars.insert(car)
return true
}
func deleteCar(id: String) -> Bool {
guard let car = cars.first(where: { $0.id == id }) else { return false }
cars.remove(car)
return true
}
func listCarsByModel(searchString: String) -> [String] {
let searchedCars = cars.filter { $0.model.lowercased().contains(searchString.lowercased()) }.sorted { $0.brand < $1.brand }
var formattedModels = [String]()
searchedCars.forEach { car in
if let car = searchedCars.first(where: { $0.model == car.model && $0.id != car.id }) {
let carName = [car.model, car.brand].joined(separator: " - ")
formattedModels.append(carName)
} else {
formattedModels.append(car.model)
}
}
return Array(formattedModels.prefix(10))
}
func listCarsByBrand(searchString: String) -> [String] {
let searchedBrands = cars.filter { $0.brand.lowercased().contains(searchString.lowercased()) }.sorted { $0.brand < $1.brand }.map { $0.model }
return Array(searchedBrands.prefix(10))
}
}

Remove objects that have duplicate keys

My code...
class Inbox {
var user = "name"
var pmsg = "label"
var match = ""
var resim = "photo"
var userID = ""
var distance = ""
var updated = ""
var isAttendingNow = ""
var isAttendingNowText = ""
init(user : String, pmsg: String, match: String, resim: String, userID : String, distance: String, updated: String, isAttendingNow: String, isAttendingNowText: String) {
self.user = user
self.pmsg = pmsg
self.match = match
self.resim = resim
self.userID = userID
self.distance = distance
self.updated = updated
self.isAttendingNow = isAttendingNow
self.isAttendingNowText = isAttendingNowText
}
}
var arrayOfRels: [Inbox] = [Inbox]()
My goal is to remove duplicate items for userID key.
How can I achieve that?
You could use a set to figure out which useIDs are unique:
func filteredRels(source [Inbox]) -> [Inbox] {
var keys: Set<String> = []
return source.filter {
if keys.contains($0.userID) {
return false
} else {
keys.insert($0.userID)
return true
}
}
}
(Banged out in the editor, so it might need some minor cleanup.)
Use Hashable
class RRR : Hashable {
var hashValue: Int = 0
static func == (lhs: RRR, rhs: RRR) -> Bool {
// in your case set only userID
return lhs.name == rhs.name && lhs.age == rhs.age
}
var name:String
var age:Int
init(name:String,age:Int) {
self.name = name
self.age = age
}
}
//
let arr = [RRR(name: "qqq", age: 12) ,RRR(name: "qqq", age: 12) , RRR(name: "hhhh", age: 12) , RRR(name: "ppppp", age: 12) ]
let set = Array(Set(arr))
print(set) // [ RRR(name: "qqq", age: 12) , RRR(name: "hhhh", age: 12) , RRR(name: "ppppp", age: 12)]
Checkout this:
extension Sequence where Iterator.Element: Hashable {
func uniqueOrdered() -> [Iterator.Element] {
return reduce([Iterator.Element]()) { $0.contains($1) ? $0 : $0 + [$1] }
}
}
class Inbox: Hashable {
...
...
static func == (lhs: User, rhs: User) -> Bool {
return lhs.userID == rhs.userID
}
}
arrayOfRels.uniqueOrdered()
You could do this in a couple of lines using a set:
var unique = Set<String>()
arrayOfRels = arrayOfRels.filter{unique.insert($0.userID).inserted}

Check if array contains element

I have a class called Person:
class Person{
let name: String?
let areas: [Area]
}
And Area consisting of the following:
class Area{
let id: String
let name: String
}
I've created a "people" array and what I want to do is check the "people" array if area.name = "Brooklyn". If it doesn't remove that person from the "people" array.
Here is what I have tried but it doesn't work:
var people = [Person]()
for s in people{
for a in s.preferredArea{
if a.areaName != "Brooklyn"{
let index = people.indexOf(s)
people.removeAtIndex(index!)
}
}
}
Can anyone point me into the right direction please?
To use indexOf, you'll have to make Person conforming the protocol Equatable:
class Person: Equatable {
let name: String
var area: [Area]
init(name: String) {
self.name = name
self.area = []
}
}
func ==(lhs: Person, rhs: Person) -> Bool {
return lhs.name == rhs.name &&
lhs.area == rhs.area // You'll have to make Area equatable too
}
Having this done, your code just works fine. If you don't want to use this method, you can use:
people.enumerate().forEach {
if !$0.1.area.contains({ $0.name == "Brooklyn" }) {
people.removeAtIndex($0.0)
}
}
Or if you can use filter to create a new array:
var newPeople = people.filter { $0.area.contains { $0.name == "Brooklyn" } }

What must you do to have set operations work on custom objects?

I made a little playground to do some tests on Set operations with custom objects but they are still failing and I have no idea why.
class User: NSObject {
let id: String
init(id: String) {
self.id = id
super.init()
}
override var hashValue: Int {
get {
return id.hashValue
}
}
override var description: String {
get {
return id
}
}
}
func ==(lhs: User, rhs: User) -> Bool {
return lhs.hashValue == rhs.hashValue
}
Then, I made two sets of User objects:
let user1 = User(id: "zach")
let user2 = User(id: "john")
let user3 = User(id: "shane")
let user4 = User(id: "john")
let user5 = User(id: "shane")
let user6 = User(id: "anthony")
let userSet1 : Set<User> = [user1, user2, user3]
let userSet2 : Set<User> = [user4, user5, user6]
But when I do an operation like so:
let newSet = userSet1.subtract(userSet2)
print(newSet)
newSet is identical to userSet1 and none of the sets are modified.
What must I do to get these set operations to work?
id:zach -> 4799450060450308971
id:john -> 4799450060152454338
id:shane -> -4799450060637667915
id:john -> 4799450060152454338
id:shane -> -4799450060637667915
id:anthony -> 4799450059843449897
id:shane -> -4799450060637667915
id:anthony -> 4799450059843449897
id:john -> 4799450060152454338
You do not get to make up your own idea of what to use as a hash value. The requirement for hashability is: the hash value of two objects must be the same if those two objects are equal.
You have provided a hash value algorithm but you have done nothing about making equatability of your User objects match it.
Here is a User object that is hashable in a set:
func ==(lhs:User, rhs:User) -> Bool {
return lhs.id == rhs.id
}
class User: Hashable, CustomStringConvertible {
let id: String
init(id: String) {
self.id = id
}
var hashValue: Int {
return id.hashValue
}
}
Observe that I have removed the complication of making this thing also an NSObject. Things are a bit different if you want to do that; you'll need to think about how NSObject works.

Sort Two Arrays of Different Types into One

I have two arrays, they are of two different objects, and both contain an ID field. What I need to do is display them in order in a table view controller. They share the same basic info, Size and ID, and those are the only pieces of data displayed, in addition to the type of object it is. When the user selects a cell then it moves to a new view that displays the finer details of the object.
Right now, I have two sections in the table, one for TypeA, and the other for TypeB. They sort through all of the items in their respective list, but are out of order for when the item was made. So it looks like:
TypeA
ID 1
ID 2
ID 5
ID 6
TypeB
ID 3
ID 4
ID 7
What I need is for it to sort them all into 1 section, and still open the detail view when selected.
Thoughts
I could put them all into an AnyObject dictionary and when looking at individual items determine if they are of one object type or the other. I feel like that would work, but how would I go about sorting that correctly?
Put all common properties into a protocol, the build and sort an array of that common protocol:
protocol HasID {
var id: Int { get }
}
class TypeA : HasID, CustomStringConvertible {
var id: Int
init(_ id: Int) {
self.id = id
}
var description : String {
return ("TypeA(\(self.id))")
}
}
class TypeB : HasID, CustomStringConvertible {
var id: Int
init(_ id: Int) {
self.id = id
}
var description : String {
return ("TypeB(\(self.id))")
}
}
let typeA = [TypeA(1), TypeA(2), TypeA(5), TypeA(6)]
let typeB = [TypeB(3), TypeB(4), TypeB(7)]
let result: [HasID] = (typeA + typeB).sorted { $0.id < $1.id }
print(result)
[TypeA(1), TypeA(2), TypeB(3), TypeB(4), TypeA(5), TypeA(6), TypeB(7)]
Alternatively to Zoff Dino answer if you do not want to burden TypeA and TypeB classes with HasID protocol then you can define extension to these classes in your view controller:
class TypeA {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
class TypeB {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
protocol HasID {
var ID: Int { get }
}
// place this in your view controller
extension TypeA: HasID {
}
extension TypeB: HasID {
}
var arrayA = [TypeA(1), TypeA(3), TypeA(5)]
var arrayB = [TypeB(2), TypeB(4)]
let sortedArray = (arrayA.map { $0 as HasID } + arrayB.map { $0 as HasID })
.sort { $0.ID < $1.ID }
You can do this like so:
class TypeA {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
class TypeB {
var ID: Int
init(_ id: Int) {
self.ID = id
}
}
struct Wrap {
var ID: Int {
return a?.ID ?? b?.ID ?? 0
}
let a: TypeA?
let b: TypeB?
}
var arrayA = [TypeA(1), TypeA(3), TypeA(5)]
var arrayB = [TypeB(2), TypeB(4)]
let sortedArray = (arrayA.map { Wrap(a: $0, b: nil) } + arrayB.map { Wrap(a: nil, b: $0)})
.sorted { $0.ID < $1.ID }
When row is selected you can determine object with:
if let a = sortedArray[index].a {
// TypeA row selected
} else if let b = sortedArray[index].b {
// TypeB row selected
}

Resources