Filter two struct arrays - ios

What is the best way to filter two Struct Arrays to match by ID and added the information in a specific property.
Example
Struct User {
let id: Int
let name: String
var arts: [Article]?
}
Struct Article {
let userId: Int
let id: Int
let title: String
let body: String
}
I have an array with all Users and other array with all Post for all Users. I need to add into User Array all post by user (User.id == Article.userId)
I'm trying to do with this.
var art = [Article]()
var users = [User]()
self?.art.forEach({ art in
guard let userId = self?.users.firstIndex(where: { $0.id == art.userId }) else {
print("Failed to find a Art by UserID")
return
}
self?.users[userId].arts?.append(art)
})
The idea is added into User Struct all Articles corresponding by user

I think your code was in the right direction. Try this approach (works for me):
var arts = [Article(userId: 1, id: 1, title: "title1"),
Article(userId: 6, id: 1, title: "title6")]
var users = [User(id: 1, name: "user1"),
User(id: 2, name: "user2")]
print("---> before users: \n \(users)")
arts.forEach{ art in
if let userNdx = users.firstIndex(where: { $0.id == art.userId }) {
if let _ = users[userNdx].arts {} else {
users[userNdx].arts = []
}
users[userNdx].arts!.append(art)
}
}
print("\n---> after users: \n \(users)")

class Article {
let userId: Int
let id: Int
let title: String
let body: String
}
class User {
let id: Int
let name: String
var arts: [Post]?
}
I think the best possible way is to convert it to a dictionary. I think the below code is well explonary.
var dict = [Int: [Article]]()
var arts = [Article]()
for art in arts {
dict[art.userId, default: []].append(art)
}
var users = [User]()
for case let user in users {
let articles = dict[user.id]
user.atrs = articles
}

Related

Correct way of creating reference to object in Realm

I create a fitness app and I use Realm as local database. During first launch I want to replace default realm with realm file which contains initial data (names of exercises, equipment, muscles engaged etc.). This initial data won't change in future. I wonder if exists some way which can help me to create reference in main class to another smaller classes. I need this to make filtering and getting data easier.
It's my main realm class
class Exercise: Object {
#Persisted var exerciseID: Int = 0
#Persisted var name: String = ""
#Persisted var category: Int
#Persisted var equipment: String
#Persisted var instruction: String
#Persisted var muscle: String
#Persisted var gif: String?
#Persisted var image: String? = nil
convenience init(name: String, category: Int, equipment: String, instruction: String, muscle: String, gif: String?, image: String?) {
self.init()
self.name = name
self.category = category
self.equipment = equipment
self.instruction = instruction
self.muscle = muscle
self.gif = gif
self.image = image
}
override static func primaryKey() -> String? {
return "exerciseID"
}
}
When I want to get all exercises and assigned equipment and muscles it is really a lot of code to retrieve this data especially when string contains few references to object.
var exercises = [Exercise]()
var equipments = [Equipment]()
func getAllExercises() {
let data = RealmService.shared.realm.objects(Exercise.self)
exercises = data.compactMap({$0})
let equipment = exercises.compactMap({$0.equipment})
for eq in exercises.compactMap({$0.equipment}) {
let numberOfEquipment = eq.components(separatedBy: ",")
for number in numberOfEquipment {
guard let intNumber = Int(number) else { return }
guard let finalEquipment = RealmService.shared.realm.object(ofType: Equipment.self, forPrimaryKey: intNumber) else { return }
equipments.append(finalEquipment)
}
}
Maybe the better option is to just insert values instead of object references?
You need to set up one-to-many relationships to take advantage of quicker queries and lazy loading.
I've simplified the models, but the magic is in the equipmentObjects property:
class Exercise: Object {
#Persisted(primaryKey: true) var exerciseID = 0
#Persisted var name: String = ""
#Persisted var equipment: String
#Persisted var equipmentObjects: List<Equipment>
convenience init(exerciseID: Int, name: String, equipment: String) {
self.init()
self.exerciseID = exerciseID
self.name = name
self.equipment = equipment
}
}
class Equipment: Object {
#Persisted(primaryKey: true) var equipmentID = 0
#Persisted var equipment: String = ""
convenience init(equipmentID: Int, equipment: String) {
self.init()
self.equipmentID = equipmentID
self.equipment = equipment
}
}
You can go ahead and initialize realm with your csv file. But when the app begins you would want to go ahead and establish the relationships between Exercise, Equipment, and Muscles. You should only do this once.
Here I've created a small utility to link the realm objects. Notice how it uses UserDefaults to check and see if relationships were already built. It is also building the relationships on a specified queue. You would want to pass in a background queue rather than the main queue so the UI doesn't lock up.
struct RealmRelationshipBuilder {
let configuration: Realm.Configuration
let userDefaults: UserDefaults
let queue: DispatchQueue
func buildRelationshipsIfNeeded(completion: #escaping() -> Void) {
guard userDefaults.didBuildRealmRelationships == false else { return completion() }
queue.async {
autoreleasepool {
defer { completion() }
do {
let realm = try Realm(configuration: configuration)
try realm.write {
realm.objects(Exercise.self).forEach { exercise in
let equipment = exercise
.equipment
.components(separatedBy: ",")
.compactMap(Int.init)
.compactMap { realm.object(ofType: Equipment.self, forPrimaryKey: $0) }
exercise.equipmentObjects.append(objectsIn: equipment)
}
}
} catch {
print("RealmRelationshipBuilder error: \(error)")
}
userDefaults.didBuildRealmRelationships = true
}
}
}
}
extension UserDefaults {
enum Key {
static let didBuildRealmRelationships = "didBuildRealmRelationshipsKey"
}
var didBuildRealmRelationships: Bool {
get { bool(forKey: Key.didBuildRealmRelationships) }
set { set(newValue, forKey: Key.didBuildRealmRelationships) }
}
}
Then to test the builder here is a small test case. But in reality you would probably want to show the user an status indicator while the relationships are being built in the background.
enum InitialData {
static let exercises: [Exercise] = {
[
Exercise(exerciseID: 1, name: "Bench press", equipment: "1,3,5"),
Exercise(exerciseID: 2, name: "Butterfly", equipment: "6"),
]
}()
static let equipment: [Equipment] = {
[
Equipment(equipmentID: 1, equipment: "Barbell"),
Equipment(equipmentID: 2, equipment: "Bench"),
Equipment(equipmentID: 3, equipment: "Bodyweight"),
Equipment(equipmentID: 4, equipment: "Cable"),
Equipment(equipmentID: 5, equipment: "Not sure"),
Equipment(equipmentID: 6, equipment: "Unknown"),
]
}()
}
class RealmExerciseTests: XCTestCase {
let realmConfiguration = Realm.Configuration.defaultConfiguration
override func setUpWithError() throws {
let realm = try Realm(configuration: realmConfiguration)
try realm.write {
realm.deleteAll()
realm.add(InitialData.exercises)
realm.add(InitialData.equipment)
}
}
func testInitialize() throws {
let relationshipBuilder = RealmRelationshipBuilder(
configuration: realmConfiguration,
userDefaults: .init(suiteName: UUID().uuidString) ?? .standard,
queue: DispatchQueue(label: "realm.init.background")
)
let expectation = expectation(description: "realm.init")
relationshipBuilder.buildRelationshipsIfNeeded {
expectation.fulfill()
}
wait(for: [expectation], timeout: 2.0)
let realm = try Realm(configuration: realmConfiguration)
realm.refresh()
guard let exercise1 = realm.object(ofType: Exercise.self, forPrimaryKey: 1) else {
return XCTFail("Missing exercise with primary key 1")
}
guard let exercise2 = realm.object(ofType: Exercise.self, forPrimaryKey: 2) else {
return XCTFail("Missing exercise with primary key 2")
}
XCTAssertEqual(exercise1.equipmentObjects.count, 3)
XCTAssertEqual(exercise2.equipmentObjects.count, 1)
}
}

Vapor 3 and Fluent - nested query

I'm trying to do a nested query in Vapor 3 and Fluent. The point is I need to get all the users, from each team where the teams have a specific eventID. Teams are children of Event. Users are children of Teams. Thanks in advance for your help.
There are only 15 teams per event, but 12 users per team
Here is the Event model:
final class Event: Codable {
var id: Int?
var name: String
}
extension Event {
var teams: Children<Event, Team> {
return children(\.eventID)
}
}
Here is the Team model
final class Team: Codable {
var id: Int?
var name: String
var icon: String
var eventID: Event.ID
}
extension Team {
var user: Parent<Team, Event> {
return parent(\.eventID)
}
}
extension Team {
var users: Children<Team, User> {
return children(\.teamID)
}
}
Here is the User model.
final class User: Codable {
var id: UUID?
var name: String
var email: String
var eventID: Event.ID
var teamID: Team.ID
}
extension User {
var user: Parent<User, Team> {
return parent(\.teamID)
}
}
I need to send an event ID and I want it to return all the users in all the teams
func getUsersForEvent(_ req: Request) throws -> Future<[User]> {
return try req.parameters.next(Event.self).flatMap(to: [User].self) { event in
return try event.teams.query(on: req).all().flatMap(to: [User].self) { team in
return try team.users.query(on: req).all()
}
}
}
You'd query that easily with raw SQL query or using SwifQL lib
Here is an example with SwifQL
struct TeamWithUsers: Content {
let id: UUID
let name, icon: String
let users: [User]
}
func getCategoriesWithProducts(_ req: Request) throws -> Future<[TeamWithUsers]> {
return try req.parameters.next(Event.self).flatMap { event in
let usersSubquery = SwifQL
.select(Fn.coalesce(Fn.array_agg(Fn.to_jsonb(User.table)), PgArray() => .jsonbArray))
.from(User.table)
.where(\User.teamID == \Team.id)
let query = try SwifQL
.select(\Team.id, \Team.name, \Team.icon, |usersSubquery | => "users")
.from(Team.table)
.where(\Team.eventID == event.requireID())
// here you could print the raw query for debugging
// print(query.prepare(.psql).plain)
return query.execute(on: req, as: .psql).all(decoding: TeamWithUsers.self)
}
}
Here's what I came up with, with help from the Ray Wenderlich book. In my task I don't need to return all the users and only need to see teams for 1 event at a time, so I pass in the eventID as a parameter.
Any guidance on how to sort the result by teamScore?
func getTeamsWithUsersForEvent(_ req: Request) throws -> Future<[TeamWithUsers]> {
let currentID = try req.parameters.next(Int.self)
print("currentID \(currentID)")
return Team.query(on: req).filter(\Team.eventID == currentID).all().flatMap(to: [TeamWithUsers].self) { team in
try team.map { team in
try team.users.query(on: req).all().map { users in
TeamWithUsers(
id: team.id,
name: team.name,
icon: team.icon,
eventID: team.eventID,
//rawScore: team.rawScore,
//users: users,
count: users.count,
teamScore: team.rawScore / users.count
)
}
}.flatten(on: req)
}
}
struct TeamWithUsers: Content {
let id: Int?
let name: String
let icon: String
let eventID: Event.ID
//let rawScore: Int
//let users: [User]
let count: Int
let teamScore: Int
}

How to filter other parent of child with Vapor?

I have this request:
router.get("/fetchOngoingReleases") { (request) -> Future<[ReleaseOut]> in
return Release.query(on: request).filter(\.inprogress == true).all().map { releases in
var result: [ReleaseOut] = []
for r in releases {
var pageEvents: [Event] = []
let num = r.releaseUsers.query(on: request).filter(\.user.fbId ~~ "something").count()
var needAuthentication: Bool
if num == 0 {
needAuthentication = true
} else {
needAuthentication = false
}
let rOut = ReleaseOut(fbId: r.fbId, name: r.name, purpose: r.purpose, needAuthentication: needAuthentication)
result.append(rOut)
}
return result
}
}
}
It says I can not access (???) releaseUser.user.fbId in the query?
Here the data model:
and in code
final class Release: Content {
var id: Int?
var fbId: String
var inprogress: Bool?
var name: String
var purpose: String
/// Creates a new `Release`.
init(id: Int? = nil, fbId: String, name: String, purpose: String = "normal selling") {
self.id = id
self.fbId = fbId
self.name = name
self.purpose = purpose
}
}
extension Release {
var releaseUsers: Children<Release, ReleaseUser> {
return children(\.releaseId)
}
}
final class ReleaseUser: Content {
var id: Int?
var releaseId: Release.ID
var userId: User.ID
init(id: Int? = nil, releaseId: Release.ID, userId: User.ID) {
self.id = id
self.releaseId = releaseId
self.userId = userId
}
}
extension ReleaseUser {
var user: Parent<ReleaseUser, User> {
return parent(\.userId)
}
}
final class User: Content {
var id: Int?
var fbId: String
var name: String
init(id: Int? = nil, fbId: String, name: String) {
self.id = id
self.fbId = fbId
self.name = name
}
}
Ok so there are several things going on here, but the main concept is that you can't just jump across different tables like that - you need to use a JOIN to join the ReleaseUser table to the User table so you can then query on the fbId
Try changing your query to:
Release.query(on: request).filter(\.inprogress == true).join(\ReleaseUser.releaseId, to:\Release.Id).join(\ReleaseUser.userId, to:\User.Id).alsoDecode(User.self).all()
The alsoDecode will give you a tuple with the first position containing your original Release instance and the second containing the corresponding User instance. So, fbId should be available as:
r.1.fbId
In your case.

Array is updating before variables are updated in Swift

I'm trying to get list of toys from Firestore and put it into array
But when I call function, it returns empty array, and just after returning it prints Toy object, so order is broken.
I thought that closures would help me, but I think I don't know how to use them, and examples from Google don't help me
Here is my code (I use SwiftUI so I created swift file with variable)
let db = Firestore.firestore()
class DataLoade {
func loadFirebase(completionHandler: #escaping (_ toys: [Toy]) -> ()){
var toysar: [Toy] = []
let toysRef = db.collection("Toys")
toysRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var name: String = document.get("name") as! String
var id: Int = document.get("id") as! Int
var description: String = document.get("description") as! String
var imageName: String = document.get("imageName") as! String
var price: String = document.get("price") as! String
var category: String = document.get("category") as! String
var timeToy = Toy(id: id, name: name, imageName: imageName, category: category, description: description, price: price)
toysar.append(timeToy)
}
}
}
completionHandler(toysar)
// print(toysar)
}
}
that's what it prints out:
[] // it prints empty array, but it is in the end of the code
Toy(id: 1001, name: "Pikachu", imageName: "pikachu-plush", category: "lol", description: "kek", price: "350₽") // and now it prints Toy object, however it is in the start of the code
Ok, so I tried to make completion handler for my function, like in "duplicated" answer, but that doesn't work: array is returning before completion handler works
ContentView.swift
func updateArray() -> [Toy]{
dl.loadFirebase() { toys in
ll = toys
}
print("lol \(datas)") // prints «lol []»
return ll
}
You can wait for an asynchronous task using a DispatchGroup. But the trick is NOT to associate asynchronous tasks with return statements. Instead, use closures to do an action after the task is done.
Disclaimer: I wrote this on SO, I apologize in advance for syntax issues.
let toyData = loadFirebase( { (toys) in
print(toys)
//Do something with toys when done
//You could add another completionHandler incase it fails.
//So 1 for pass and 1 for fail and maybe another for cancel. W/e u want
} )
let db = Firestore.firestore()
func loadFirebase(completionHandler:#escaping ((toys: [Toy]?) -> Void)) {
//Create Group
let downloadGroup = DispatchGroup()
var toysar: [Toy] = []
let toysRef = db.collection("Toys")
//If you had multiple items and wanted to wait for each, just do an enter on each.
downloadGroup.enter()
toysRef.getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting documents: \(err)")
} else {
for document in querySnapshot!.documents {
var name: String = document.get("name") as! String
var id: Int = document.get("id") as! Int
var description: String = document.get("description") as! String
var imageName: String = document.get("imageName") as! String
var price: String = document.get("price") as! String
var category: String = document.get("category") as! String
var timeToy = Toy(id: id, name: name, imageName: imageName, category: category, description: description, price: price)
toysar.append(timeToy)
print(timeToy)
}
//We aren't done until AFTER the for loop, i.e., each item is grabbed.
downloadGroup.leave()
}
}
//Once the queue is empty, we notify the queue we are done
downloadGroup.notify(queue: DispatchQueue.main) {
completionHandler(toys)
}
}
import SwiftUI
var dl = DataLoade()
var ll: [Toy] = []
let semaphore = DispatchSemaphore(value: 1)
struct ContentView: View {
var items: [Toy]
var body: some View {
NavigationView{
ScrollView(){
VStack(alignment: .leading){
ToyRow(category: "Наш выбор", toys: items)
Spacer()
ToyRow(category: "Акции", toys: items)
}
}.navigationBarTitle(Text("Игрушки г.Остров"))}
}
}
func upe(completionHandler:#escaping ((toys: [Toy]?){
dl.loadFirebase(completionHandler: { toy in
ll.append(contentsOf: toy!)
completionHandler(ll)
} )
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
upe(completionHandler: { (toys) in
DispatchQueue.main.async {
ContentView(items: toys)
}
})
}
}

How to combine arrays depending on user selection?

I want to see what user selected like the name of book and its assocaited chapters
I did this
struct bookChpt {
var book:[String] = []
var chapter:[[Int]] = []
}
let chptSelected = [bookChpt(book:bookArr,chapter:chptArr)]
var bookArr:[String] = []
var chptArr:[[Int]] = []
I have this in viewDidLoad()
if let bTitle = result.value(forKey: "bookTitle") as? String
{
bookArr.append(bTitle)
}
if let cNo = result.value(forKey: "chpNo") as? [Int]
{
chptArr.append(cNO)
}
print(chptSelected)
I am getting this
bookChpt( book: ["Hobbit", "LOTR"], chapter: [[3,5],4])
but I like to see this
["Hobbit", 3, 5], ["LOTR", 4]
There are a couple of possibilities. You could add a function to the struct to display its contents in the way you want:
struct BookChapter {
var book:[String] = []
var chapter:[[Int]] = []
func display() -> [[Any]] {
var output = [[Any]]()
for i in 0..<book.count {
output.append([book[i], chapter[i]])
}
return output
}
}
Or you could modify the structure of the struct to contain the book and chapters as tuples:
struct BookChapter {
var book:[(String, [Int])]
}
Going a bit further, anywhere you see a loop - such as in the display function above - you might also consider using map to achieve the same thing:
func display() -> Any {
return book.enumerated().map { $0.element + " " + chapter[$0.offset].description }
}
If you use an Dictionary like this, you can print the key and value whatever way you wanted.
var bookChapters = [String: [Int]]()
bookChapters["Hobbit"] = [1,2,3]
bookChapters["Hobbit"]?.append(contentsOf: [4])
for (book, chapter) in bookChapters {
print("\(book): \(chapter)")
}
Change your struct to
struct BookChapt {
var book: String = ""
var chapter: [Int] = []
}
and in viewDidLoad()
var bookName = ""
var chapters:[Int] = []
if let bTitle = result.value(forKey: "bookTitle") as? String
{
bookName = bTitle
}
if let cNo = result.value(forKey: "chpNo") as? [Int]
{
chapters = cNo
}
let chptSelected = BookChapt(book: bookName, chapter: chapters)
print(chptSelected)

Resources