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]
}
}
Related
Im trying to sort the columns of a CSV file,the contents of the CSV is provided in string
Beth,Charles,Danielle,Adam,Eric\n
17945,10091,10088,3907,10132\n
2,12,13,48,11
Converted String to 2D Array
[["Beth", "Charles", "Danielle", "Adam", "Eric"], ["17945", "10091", "10088", "3907",
"10132"], ["2", "12", "13", "48", "11"]]
How can i sort the only the first dimension of the 2D array or the Names in the 2D Array and still keep the mappings of the other dimension, i don't know how to explain this properly, but i hope the details below will help you understand what i want to achieve.
Adam,Beth,Charles,Danielle,Eric\n
3907,17945,10091,10088,10132\n
48,2,12,13,11
I want to achieve this with the names sorted and the other values in the other arrays mapping to the names like below,
[["Adam", "Beth", "Charles", "Danielle", "Eric"], ["3907", "17945", "10091", "10088",
"10132"], ["48", "2", "12", "13", "11"]]
Using this approach is not working but sorts the whole array
let sortedArray = 2dArray.sorted(by: {($0[0] as! String) < ($1[0] as! String) })
[["3907", "17945", "10091", "10088", "10132"], ["48", "2", "12", "13", "11"], ["Adam", "Beth", "Charles", "Danielle", "Eric"]]
Below if the full code
var stringCSV =
"Beth,Charles,Danielle,Adam,Eric\n17945
,10091,10088,3907,10132\n2,12,13,48,11";
var csvFormatted = [[String]]()
stringCSV.enumerateLines { line , _ in
var res = line.split(separator: ",",omittingEmptySubsequences:
false).map{ String($0) }
for i in 0 ..< res.count {
res[i] = res[i]
}
csvFormatted.append(res)
}
print(csvFormatted)
let sortedArray = csvFormatted.sorted(by: {($0[0] as! String)
< ($1[0] as! String) })
print(sortedArray)
Using "associated" arrays always ends up being messy.
I would start by creating a struct to represent each object (You haven't said what the numbers are, so I have picked a couple of property names. I have also kept String as their type, but converting to Int is possibly better, depending on what the data actually represents).
struct Person {
let name: String
let id: String
let age: String
}
Now you can combine the arrays and use that to build an array of these structs. Then you can sort by the name property.
let properties = zip(sourceArray[1],sourceArray[2])
let namesAndProperties = zip(sourceArray[0],properties)
let structArray = namesAndProperties.map { (name,properties) in
return Person(name: name, id: properties.0, age: properties.1)
}
let sortedArray = structArray.sorted {
return $0.name < $1.name
}
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
}
i have json objects like and parsed in an array
let objects = [Object]()
struct Object {
name: String
id: Int
}
Suppose like
let objects [Object(name:oscar, id: 11), Object(name:sanchez, id: 12),Object(name:emily, id: 15),Object(name:clarck, id: 31) ... ]
How can i take the string array as below also with this name which object belongs to ? ( so i can use object easily)
let stringPropertyArray = [oscar, sanchez,emily,clarck ... ]
Thanks
how i will find the object ? if you have "emily" and i want to item.id which emily belongs to ?
Perhaps you want something like
if let ob = objects.first {$0.name == "emily"} {
print(ob.id)
}
But if your goal is to search quickly, it would be better to have a dictionary keyed by the value you will be searching on.
I think this is what you want
let stringPropertyArray: [String] = objects.map {$0.name}
There are 2 approaches you can use:
by looping (traditional approach)
var listName: [String] = []
for item in objects {
listName.append(item.name)
}
by using higher order function
let listName = objects.map{ $0.name }
There would be a case if your name property is optional and for some object, name property value is nil then we should use compactMap higher order function in order to avoid nil object in the list
let listName = objects.compactMap{ $0.name }
To find any specific object we can use filter like below:
let object = objects.filter{
$0.name == "sanchez" }.first
// OR
let object = objects.first { object -> Bool in
object.name == "emily" }
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)
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.