Mapping array of objects into new array of array by value - ios

Hey i have array of objects, my goal is to split them into several arrays by common property value for example:
struct Person {
let name: String
let city: String
}
let persons: [Person] = [Person(name: "John", city: "San Francisco"),
Person(name: "Tim", city: "San Francisco"),
Person(name: "Bob", city: "Atlanta")]
my goal is to get arrays that contain only persons from same city.
In that example result will be two arrays first contain John and Tim's objects, and 2nd contain only Bob's object.
Thanks

If I got you correctly, you should use a dictionary and group items by the city property:
let grouped = Dictionary(grouping: persons) { $0.city }
If you don't need the keys, then you can map them on values like:
let groupedWOKeys = grouped.map { $1 }

Related

same value in dictionary in swift

I m mapping data that come from service with using dictionary [String: String]. I collect them dictionary array. For example, if their parent ids are the same, I want to add their values by array value.
["ParentId": "1","Value": "["Giyim","Aksesuar","Ayakkabı"]"]
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
Here is my code and its output.
struct Categories {
let parentId: String
let values: [String]
}
for result in results {
if result?.parentCategoryId != "" {
for docId in self.docIds {
if result?.parentCategoryId == docId {
//print(result?.name)
var values = [String]()
values.append(result?.name ?? "")
self.newCat.append(Categories(parentId: docId, values: values))
}
}
}
}
Problem
As far as I understand from the description you want to map some service data structure to a dictionary where key is parentId and value is an array of some items referred to parentId.
I believe your problem comes from a misunderstanding of the concept of dictionary as a data structure.
[String: String] is dictionary where keys and their associated values are of String type. For example:
["firstKey": "firsthValue", "secondKey": "secondValue", ...]
That means you cannot store associated values of String and Array types in the same dictionary, as you already told the compiler you would like to store only strings.
It is also the reason I don't know parent id sometimes on the left sometimes on the right in photo
This is because key-value pairs are stored in the dictionary without order. This is how dictionaries work :) I'd strongly recommend reading some short official materials to get used to them.
New Swift 5.4 version has a new OrderedDictionary data structure, where keys are ordered, but there is absolutely 100500% no reason to use it for your problem*
Possible solutions
In your case i would suggest either use some struct:
struct SomeData {
let parentID: Int
var values: [String]
}
var storage: [SomeData] // alternative to emptyDic
// Filling storage with data from service
for result in inputData {
// search for SomeData with required id, add value
// OR create SomeData if there is no such id in array yet
}
OR [personally this appeals to me more]
Store data in [String: [String]] dictionary, where the key is parentID and the associated value is an array of values.
The algorithm of filling this dictionary is pretty the same:
You add new key-value pair for every new parentID
You append new values for parentIDs that are already in the dictionary.
Using the struct approach, you could do something like this (you'll need to adapt it to your code, but that should be straightforward):
struct Categories {
let parentId: String
var values: [String] //notice this needs to be a var, not a let
}
func addItem(categories : inout [Categories], docId: String, name: String) {
if let index = categories.firstIndex(where: { $0.parentId == docId }) {
categories[index].values.append(name)
} else {
categories.append(Categories(parentId: docId, values: [name]))
}
}
func addValues() {
var categories = [Categories]()
addItem(categories: &categories, docId: "4", name: "Test1")
addItem(categories: &categories, docId: "1", name: "Test")
addItem(categories: &categories, docId: "4", name: "Test2")
addItem(categories: &categories, docId: "4", name: "Test3")
print(categories)
//in your code, it'll look more like:
// addItem(categories: &self.newCat, docId: docId, name: result?.name ?? "")
}
Which yields this:
[
StackOverflowPlayground.Categories(parentId: "4", values: ["Test1", "Test2", "Test3"]),
StackOverflowPlayground.Categories(parentId: "1", values: ["Test"])
]
I still wonder whether you maybe just want a Dictionary that is keyed by the parentId, but not knowing your use case, it's hard to say.

Swift - update object in place

If I want to update existing class object is there any easier way than duplicating init to update method? (my Person object has about ten properties which are defined firstly or can be edited at later app usage)
problems listed in example below:
person1 reinitialization does not update reference to a person1 object in a parent-child array - child array does not contain updated object
person2 object behaves as wanted (child array contains updated object) but uses duplicated initializer code in update method to make “edit in place” which looks really bad in terms of DRY
any hints how to do it Swiftly?
import UIKit
class Person {
var name: String
var surname: String
var age: Int?
init(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
func update(name: String, surname: String, age: Int? = nil) {
self.name = name
self.surname = surname
self.age = age
}
}
class Parent {
var child = [Person]()
}
var parent = Parent()
var person1 = Person(name: "John", surname: "Doe")
print(person1.name)
parent.child.append(person1)
person1 = Person(name: "Jack", surname: "Doe")
print(person1.name)
print(parent.child.first!.name)
var person2 = Person(name: "Tom", surname: "Cruise")
print(person2.name)
parent.child.append(person2)
person2.update(name: "Jim", surname: "Cruise")
print(person2.name)
print(parent.child.last!.name)
to make it more clear think about the array of user profiles where you can add a new profile or edit an existing one. and above problem is about editing existing user profile
Here is the explanation for both of your queries,
1. parent child array contains the reference to the person1 object, i.e. person1 and parent child array store the reference to same object.
var person1 = Person(name: "John", surname: "Doe")
parent.child.append(person1)
Now, you're assigning a new object to person1 instead of making changes to the same object, i.e.
person1 = Person(name: "Jack", surname: "Doe")
This won't reflect in the parent child array. It will still contain the reference to the previous Person object.
This is the reason you are getting different values of name in person1 and parent child array
print(person1.name) //Jack
print(parent.child.first!.name) //John
2. Instead of adding a separate method for updating the object - update(name:surname:age:), you can simply update the values individually using the . operator on object's properties, i.e
var person2 = Person(name: "Tom", surname: "Cruise")
parent.child.append(person2)
person2.name = "Jim"
Since in the above code, instead of assigning a new Person object to person2, we're just updating its values.
So, person2 and parent child array's object will still reference the same object and hence will print the same name values.
print(person2.name) //Tom
print(parent.child.last!.name) //Tom

SQL type fetch request from core data

Although there are similar topics to this, but in stack overflow there is no clear answer to issue I will describe below. For simplicity I will simplify entities. So: there are two entities in core data:
First entity called Person with attributes:
FirstName
LastName
City
Second entity: Country with attributes:
City
Capital
The idea is that in one view controller, user adds values by form for second entity - fills in city and capital information for country.
In other view controller, user fills first and last names but selects city from picker view that is already filled from data added in other view controller.
In third view controller, I want to fetch first entity adding capital to it (all info in table view controller), e.g. if first entity contains data: John | Jones | Chicago the second one by default should have a line with this data:
Chicago | Washington
How can I get "Washington" assigned to "John"?
Similar to this problem, but no clear answer there: Selecting columns from different entities
I came up with next db scheme:
Entity:
City
Attributes:
id: Integer 16
name: String
Relationships:
[o] capitalOfCountryOptional: Country, inverse: capital
[o] country: Country, inverse: cities
[m] people: Person, inverse: city
Country
Attributes:
id: Integer 16
name: String
Relationships:
[o] capital: City, inverse: capitalOfCountryOptional
[m] cities: Person, inverse: city
Person
Attributes:
id: Integer 16
firstName: String
flastName: String
Relationships:
[o] city: City, inverse: people
Once you need some person info and you know id:
Now you can create all needed obects and set as relationships:
let country = ... Ukraine
let city = ... Lviv
let capital = ... Kyiv
let person = ... John Doe
country.capital = capital
city.country = country
person.city = city
Then you need to save yuor context and you can retrieve person info:
import CoreData
public class MOPerson: NSManagedObject {
static func getPerson(id: Int16) -> MOPerson? {
let request: NSFetchRequest<MOPerson> = MOPerson.fetchRequest()
request.predicate = NSPredicate(format: "\(#keyPath(MOPerson.id)) = %#", NSNumber(int16: id))
request.fetchLimit = 1
let result: MOPerson? = (try? CoreDataController.shared.managedContext.fetch(request))?.first
return result
}
}
Example:
if let person = MOPerson.getPerson(id: 1) {
print("Person: \(person.firstName), capital: \(person.city?.country?.capital?.name ?? "not defined")")
}

Swift: Associative Arrays in UITableView

Pretty new so apologies if this is noobish.
I'm trying to get the key and the value from an associative array to print out as the label text in a cell.
At the moment I have the array:
let users = ["John","James","Liam"]
and I am getting the value for the cell text like so:
cell.textLabel!.text = self.users[indexPath.row]
Which will give me rows of the names in. I am struggling when I add in user ages like so
lets users = ["John" : 36, "James": 12, "Liam": 30]
I get this error:
Ambiguous reference to member 'subscript'
How do I get the cell text to display both the name and age?
If you really need to refer to the data via an array, there are at least two ways you can easily accomplish this. Create a Person object like in the comments, or you can quickly use a tuple structure. The following code works in Xcode Playground (Swift 3, 4).
// - First Approach
struct Person {
var name: String
var age: Int
}
let users = [Person(name: "John", age: 36), Person(name: "James", age: 12), Person(name: "Liam", age: 30)]
// say indexPath.row = 0
print("name: \(users[0].name), age: \(users[0].age)") // name: John, age: 36
// - Second Approach
let usersTuple = [("John", 36), ("James", 12), ("Liam", 30)]
print("name: \(usersTuple[0].0), age: \(usersTuple[0].1)") // name: John, age: 36
You need to define a dictionary and with ages and names and in each cell get from the dictionary the age with the name as key, or you can define a model with name and age like Person and put and use as your datasource Array, which I think is better
class Person{
var age : Int = 0
var name : String = ""
init(name:String,age:Int){
self.name = name
self.age = age
}
}
declare an array of Person in your ViewController
var users = [Person(name: "John", age: 36),Person(name: "James", age: 12),Person(name: "Liam", age: 30)]
In the cellForRow method
let currUser = self.users[indexPath.row]
cell.textLabel!.text = currUser.name + "Age: \(currUser.age)"
If you're coming from a PHP style background, it can be confusing that dictionaries look like associated arrays but there's no way to reference them by index like in PHP. You'll need to reference by the key (the string) or convert them into something which you can retrieve later.
Luckily in Swift you can just use something called a tuple, which looks like this:
let user = ("John", 36)
And you reference the values like this:
let name = user.0
let age = user.1
You can store tuples in arrays, so you can do this:
let users = [("John", 36), ("James", 12)]
let johnAge = users[0].1
You can also give the tuple named parameters but by this point you might as well create a struct. Here's what it looks like anyway:
let user = (name: "John", age: 36)
let johnAge = user.age
Another neat trick with tuples is that if you typealias it you can create it the same way you would a struct, without having to device the properties individually, like this:
typealias User = (name: String, age: Int)
let john = User(name: "John", age: 36)
By this point it's just a case of saving a few lines of code but it really depends on how lightweight you need the user object to be. You wouldn't be able to make it conform to anything in the future, which is why a struct can be a better option. If all you ever need is a lightweight object, tuples are great.

Compare objects and edit array [duplicate]

This question already has answers here:
How to group by the elements of an array in Swift
(16 answers)
Closed 6 years ago.
I want to create array of unique elements by specific property.
Ex:
I have array of objects (Person) :
struct Person {
var name: String?
var secondName: String?
init (name: String, secondName: String) {
self.name = name
self.secondName = secondName
}
}
let person1 = Person(name: "name1", secondName: "secondName1")
let person2 = Person(name: "name2", secondName: "secondName2")
let person3 = Person(name: "name1", secondName: "secondName3")
let personsArray = [person1, person2, person3]
I want to get new array, that will contain person objects with unique name
something like this $0.name == $1.name
What is the best way to achieve that ?
Result should be arrays of objects with unique name param = [[person1, person3], [person2]]
This is my personal interpretation of your question
Given an array of Person(s) you want in output several dictionaries where the key is the name of a person and the value is a list of persons with that name.
Here's the code
let dict = persons.reduce([String:[Person]]()) { (dict, person) -> [String:[Person]] in
var dict = dict
dict[person.name] = (dict[person.name] ?? []) + [person]
return dict
}
One approach: You could add them one by one to a dictionary where "name" is the key (consider using lowercase for it), and "array of Persons" is the value. When done, the keys array will have all your unique "name" values, and each key's value will be the array of Persons with that "name". You could then "trim" your dictionary by removing any key with an array that has a count less than 2.
Alternative: Sort the array by "name", then you can easily remove any that don't appear twice (if an element doesn't match one of it's neighbors, then remove it).

Resources