Print out properties that are not equal between two objects - swift

Say I have a User class with three properties: name, email, and fruits. What's the most efficient way to determine the differences, and print them out in a dictionary alongside the property name?:
struct User: Equatable {
let name: String
let email: String
let fruits: [Fruit]
}
// Old user
let user = User(name: "Jane", email:"ja#ne.com", fruits: [.banana, .apple])
// Updated user
let updatedUser = User(name: user.name, email: user.email, fruits: [.apple, .peach])
// Looking for help writing a function that can efficiently find the changes + format them into a dictionary for Firebase etc:
let updatedProperties = updatesBetween(old: user, new: updatedUser)
// Output:
//["fruits": ["apple", "peach"]]

Take advantage of the fact that these objects are Equatable and check for their equality right off the bat before proceeding, and then go property by property and look for differences. Keep in mind that arrays care about order so [.apple, .banana] will be different from [.banana, .apple]. If you don't care about order then just sort them both before comparing (this may require additional steps depending on the contents of the array). You can also consider translating the arrays into sets before comparing (if the arrays don't contain duplicates).
func getDifferencesBetween(old: User, new: User) -> [String: Any] {
guard old != new else {
return [:]
}
var differences: [String: Any] = [:]
if old.name != new.name {
differences["name"] = new.name
}
if old.email != new.email {
differences["email"] = new.email
}
if old.fruits != new.fruits {
differences["fruits"] = new.fruits
}
return differences
}

Related

Swift - Array to Dictionary

So, I have the following objects:
struct Person {
let name: String
let birthday: String
}
let Bob = Person(name: "Bob", birthday: "11.12.1987")
let Tim = Person(name: "Tim", birthday: "11.12.1987")
let John = Person(name: "John", birthday: "01.02.1957")
let Jerry = Person(name: "Jerry", birthday: "17.12.2001")
And the following array:
let people = [Bob, Tim, John, Jerry]
My goal is to generate a dictionary from this array, with "birthday" for the key and the "Person" object itself as the value: [String: [Person]]. If there are equal keys the person should be added and form an array as a key. So the result will be the following:
dictionary = ["11.12.1987": [Bob, Tim], "01.02.1957": John, "17.12.2001": Jerry]
What is the best way to achieve this?
Cheers!
You could use Dictionary(grouping:by:) method together with mapValues,
let result = Dictionary(grouping: people, by: \.birthday)
print(result)
Output is,
["01.02.1957": ["John"], "17.12.2001": ["Jerry"], "11.12.1987":
["Bob", "Tim"]]
This would yield you a Dictionary of type Dictionary<String, [Person]>.
From, your question it seems like you want a Person if there is only one Person and array when there are multiple people. You would loose type in that situation and your type would be something like this, Dictionary<String, Any>. You could make small bit of modification to the above code to do that.
let result = Dictionary(grouping: people, by: \.birthday)
.mapValues { people -> Any in
if people.count == 1 {
return people.first!
}
return people
}
print(result)
The output would be similar to what you want,
["01.02.1957": "John", "17.12.2001": "Jerry", "11.12.1987": ["Bob",
"Tim"]]
What about a loop through the dictionary checking if date exists. Otherwise create key with date and assign value.
If key already exists append person to the actual key value:
var dictionary = [String: [Person]]()
// Loop through all person objects in array
for person in people {
// Check if date already exists
if dictionary[person.birthday] != nil {
// If already exists, include new person in array
var array = dictionary[person.birthday]
array!.append(person)
}
// If date does not exists, add new key with date and person
else {
dictionary[person.birthday] = [person]
}
}

Problem with mapping an array of objects in Swift

I have the following 2 class / structs:
class ConversationDetails {
var messages: [ChatMessage]?
var participants: [User]?
}
class User: Codable {
init (email: String) {
self.email = email
}
// system
var id: String?
var verifiedaccount: Int?
var rejected: Int?
...
}
I've further got the var conversationDetails = ConversationDetails () and I'm populating it with an API call. That all works fine.
I'd like to map the participants array inconversationDetailsand access the id property of each participant like so:
let recipient_ids = self.conversationDetails.participants.map( { (participant) -> String in
return participant.id
})
In my understanding, map iterates over the entire participants array, which is an array of User objects and I can access each item via participant.
However, I get Value of type '[User]' has no member 'id' for return participant.id.
Where is my misunderstanding?
Your participants var is optional, so you need to add question mark to access array. Without it, you try to call map on optional.
This is working code:
let recipientIds = conversationDetails.participants?.map( { (participant) -> String in
return participant.id
})
or shorter:
let recipientIds = conversationDetails.participants?.map { $0.id }
Also, you can use compactMap to remove nils from recipientIds array and have [String] array instead of [String?]:
let recipientIds = conversationDetails.participants?.compactMap { $0.id }
The first problem is that participants is optional so you need to add a ? when accessing it
conversationDetails.participants?
then when mapping you should use compactMap since id is also an optional property
let recipient_ids = conversationDetails.participants?.compactMap { $0.id }
Another variant is to not have an optional array but instead initialize it to an empty array. This is actually a much better way to handle collection properties because you can have a cleaner code by initializing them to an empty collection
var participants = [User]()
and then do
let recipient_ids = conversationDetails.participants.compactMap { $0.id }

filter dictionary of type [String: [object]] for specific key in object arrays

I'm showing users a tableview that looks like the contacts view in the sense that the data source is a dictionary of type [String: [User]] and so I'm showing section headers with the first letter of the Users name.
Now, I want to allow them to search by the users first name... but I can't get this to work.
These are the dictionaries... the friends will hold data like this ["A" :[Users whose first name start with an A]] and so on
var friends = [String: [User]]()
var filteredFriends = [String: [User]]()
This is my filtering code with searchText being the first name I want to search by
self.filteredFriends = self.friends.filter{friend in
return friend.firstName.lowercased().contains(searchText)}
What I want to do is make filteredFriends have all of the values of friends whose users first names start with the text.
How do I make this work with a dictionary like mine?
Thanks
More Info:
Class User {
var firstName: String?
init(name: String){
self.firstName = name
}
}
Sample scenario:
friends = ["A" : [User(name: "Anthony), User(name: "Arnold")], "B" : [User(name: "Barry")]]
filteredFriends = friends
searchText = "an"
Desired filteredFriends (end result) = ["A" : [User(name: "Anthony)]]
There's no simple way. You just have to cycle through the whole dictionary, like this:
// these are your initial conditions
class User {
let firstName: String
init(name: String) {
self.firstName = name
}
}
let friends : [String:[User]] =
["A" : [User(name: "Anthony"), User(name: "Arnold")],
"B" : [User(name: "Barry")]]
let searchText = "an"
// and here we go...
var filteredFriends = [String:[User]]()
for entry in friends {
let filt = entry.value.filter{$0.firstName.lowercased().hasPrefix(searchText)}
if filt.count > 0 {
filteredFriends[entry.key] = filt
}
}
Now filteredFriends contains the desired result.
Ideally, you would use map to transform the input dictionary to the output dictionary, by filtering each of its values (User arrays). Unfortunately, Dicationary.map returns [(Key, Value)] (an array of `(Key, Value) tuples), not a new Dictionary. I'll take the more conventional, iterative approach instead.
Note: For those who are about to circle jerk around using functional styles everywhere, who are about to suggest using reduce: no. Pull out a profiler, and look at how slow and power-consuming it is to generate dictionaries with reduce. Especially important on a mobile device with limited battery.
Here's how I would do it:
let allFriends = [String: [User]]()
var filteredFriends = [String: [User]]()
let searchText = "a".lowercased()
for (initial, allFriendsWithInitial) in allFriends {
if initial.lowercased() == searchText {
// don't even have to bother filtering `friends`
filteredFriends[initial] = allFriendsWithInitial
}
let filteredFriendsWithInitial = allFriendsWithInitial.filter{
$0.firstName.lowercased().contains(searchText)
}
if !filteredFriendsWithInitial.isEmpty {
filteredFriends[initial] = filteredFriendsWithInitial
}
}
print(filteredFriends)

How to sort an array of Structures with/by dynamic property

Given an NSTableView that has an array of structures as its datasource. A user can click on any column heading to sort by that column. The column identifiers match the property names of the properties within the structure.
Given a structure
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
}
and an array of structures
var myArray = [MyStructure]()
The goal is that when a column heading is clicked, use that column's identifier to sort the array of structures by that column identifier/property
With an array of dictionaries, it was easy...
self.myArrayOfDictionaries.sortInPlace {
(dictOne, dictTwo) -> Bool in
let d1 = dictOne[colIdentifier]! as String;
let d2 = dictTwo[colIdentifier]! as String;
return d1 < d2 //or return d1 > d2 for reverse sort
}
The question is how to access the properties of the Structure dynamically, something like
let struct = myArray[10] as! MyStructure //get the 10th structure in the array
let value = struct["col0data"] as! String //get the value of the col0data property
If there is a better way, suggestions would be appreciated.
I should also note that the structure may have 50 properties so this is an effort to reduce the amount of code needed to sort the array by any one of those properties.
edit:
One solution is to change the structure to a class derived from NSObject. Then the properties could be accessed via .valueForKey("some key"). However, I am trying to keep this Swifty.
Maybe I have a solution to your problem. The advantage of this code over your solution is here you don't need to add a subscript method to your struct to create an hardcoded String-Property-Value map via code.
Here's my extension
extension _ArrayType {
func sortedBy(propertyName propertyName: String) -> [Self.Generator.Element] {
let mirrors = self.map { Mirror(reflecting: $0) }
let propertyValues = mirrors.map { $0.children.filter { $0.label == propertyName }.first?.value }
let castedValues = propertyValues.map { $0 as? String }
let sortedArray = zip(self, castedValues).sort { (left, right) -> Bool in
return left.1 < right.1
}.map { $0.0 }
return sortedArray
}
}
Usage
struct Animal {
var name: String
var type: String
}
let animals = [
Animal(name: "Jerry", type: "Mouse"),
Animal(name: "Tom", type: "Cat"),
Animal(name: "Sylvester", type: "Cat")
]
animals.sortedBy(propertyName: "name")
// [{name "Jerry", type "Mouse"}, {name "Sylvester", type "Cat"}, {name "Tom", type "Cat"}]
animals.sortedBy(propertyName: "type")
// [{name "Tom", type "Cat"}, {name "Sylvester", type "Cat"}, {name "Jerry", type "Mouse"}]
Limitations
The worst limitation of this solutions is that it works only for String properties. It can be change to work with any types of property by it must be at compile time. Right now I have not a solution to make it work with any king of property type without changing the code.
I already asked help for the core of the problem here.
I would definitely recommend simply embedding your dictionary into your struct. A dictionary is a much more suitable data structure for 50 key-value pairs than 50 properties – and you've said that this would be an acceptable solution.
Embedding the dictionary in your struct will give you the best of both worlds – you can easily encapsulate logic & you have have easy lookup of the values for each column ID.
You can now simply sort your array of structures like this:
struct MyStructure {
var dict = [String:String]()
init(col0Data:String, col1Data:String) {
dict["col0data"] = col0Data
dict["col1data"] = col1Data
}
}
var myArray = [MyStructure(col0Data: "foo", col1Data: "bar"), MyStructure(col0Data: "bar", col1Data: "foo")]
var column = "col0data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // [MyStructure(dict: ["col0data": "bar", "col1data": "foo"]), MyStructure(dict: ["col0data": "foo", "col1data": "bar"])]
column = "col1data"
myArray.sort {
$0.dict[column] < $1.dict[column]
}
print(myArray) // MyStructure(dict: ["col0data": "foo", "col1data": "bar"])], [MyStructure(dict: ["col0data": "bar", "col1data": "foo"])
Here's an answer (but not the best answer); use subscripts to return the correct property, and set which property you are sorting by within the array.sort:
struct MyStructure {
var col0data = "" //name matches the column identifier
var col1data = ""
subscript(key: String) -> String? { //the key will be the col identifier
get {
if key == "col0data" {
return col0data
} else if key == "col1data" {
return col1data
}
return nil
}
}
}
And then here's how the sort works:
let identifier = the column identifier string,say col0data in this case
myArray.sortInPlace ({
let my0 = $0[identifier]! //the identifier from the table col header
let my1 = $1[identifier]!
return my0 < my1
})
If you do not know what types the values of MyStructure can be you will have a hard time comparing them to sort them. If you had a function that can compare all types you can have in MyStructure then something like this should work
struct OtherTypeNotComparable {
}
struct MyStructure {
var col0data = "cat" //name matches the column identifier
var col1data: OtherTypeNotComparable
}
let structures = [MyStructure(), MyStructure()]
let sortBy = "col1data"
func yourCompare(a: Any, b: Any) -> Bool {
return true
}
var expanded : [[(String, Any, MyStructure)]]
= structures.map { s in Mirror(reflecting: s).children.map { ($0!, $1, s) } }
expanded.sortInPlace { (a, b) -> Bool in
let aMatch = a.filter { $0.0 == sortBy }.first!.1
let bMatch = b.filter { $0.0 == sortBy }.first!.1
return yourCompare(aMatch, b: bMatch)
}
source: https://developer.apple.com/library/watchos/documentation/Swift/Reference/Swift_Mirror_Structure/index.html

Swift dictionary all containing

Lets say I have dictionaries like below and wanted an array of red dogs. I figured I need to get an array of all the names of the type "dog" using the first dictionary, and then use the name key and the color to search the final dictionary to get ["Polly,"jake"]. I've tried using loops but can't figure out how to iterate through the dictionary.
var pets = ["Polly" : "dog", "Joey" : "goldfish", "Alex" : "goldfish", "jake" : "dog"]
var petcolor = ["Polly" : "red", "Joey" : "black", "Alex" : "yellow", "jake":red"]
The correct solution would seem to be to create a Pet struct (or class) and collate all of this information into a struct and build either an array or dictionary full of these values.
struct Pet {
let name: String
let type: String
let color: String
init(name: String, type: String, color: String) {
self.name = name
self.type = type
self.color = color
}
}
Now, let's build an array of these pets:
var goodPets = [Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets.append(Pet(name: petName, type: petType, color: petColor))
}
Now that we've filled out goodPets, pulling out any particular subset of Pets becomes very easy:
let redDogs = goodPets.filter { $0.type == "dog" && $0.color = "red" }
And although this answer looks like a lot of set up & legwork compared to other answers, the major advantage here is that once we build the goodPets array, any way we want to scoop pets out of there ends up being more efficient. And as we increase the number of properties the pets have, this becomes more and more true compared to the other answers.
If you'd rather store our model objects in a dictionary continuing to use the names as the keys, we can do that as well, but the filter looks a little bit stranger.
Building the dictionary looks mostly the same:
var goodPets = [String : Pet]()
for (petName, petType) in pets {
guard let petColor = petcolor[petName] else {
// Found this pet's type, but couldn't find its color. Can't add it.
continue
}
goodPets[petName] = (Pet(name: petName, type: petType, color: petColor))
}
But the filter is slightly different:
let redDogs = goodPets.filter { $0.1.type = "dog" && $0.1.color = "red" }
Note that in both cases, redDogs has the type [Pet], that is, an array of Pet values.
You can iterate through a dictionary like this:
for key in pets.keys() {
if pets[key] == "Dog" {
}
}
Or:
for (name, pet) in pets {
if pet == "Dog" {
}
}
nhgrif is probably correct about structure but, to answer the literal question:
let dogs = Set(pets.filter { $0.1 == "dog" }.map { $0.0 })
let redAnimals = Set(petscolor.filter { $0.1 == "red" }.map { $0.0 })
let redDogs = dogs.intersect(redAnimals)
Each filter is a block that operates on a (key, value) tuple, testing the value and ultimately creating a dictionary with only the matching (key, value) pairs. Each map then converts that filtered dictionary into an array by discarding the values and just keeping the keys.
Each array is turned into a set to support the intersect operation. The intersect then determines the intersection of the two results.