Reputation: 647
I have two structs in Swift like this:
struct Friend {
id: Int
name: String
biography: String
profilePicURL: String
}
struct ProfileViews {
id: Int
views: Int
}
I am trying to sort an array of Friends ([Friend]
) based on the amount of views for the profile ([ProfileViews]
). How can I do this based on the id, which is the same in both structs? The catch is, sometimes both structs won't match. It can be, for example, that a certain Friend doesn't have ProfileViews yet. For example:
Friend(id: 1, name: "PennyWise", biography: "Lorem ipsum", "test.jpg")
Friend(id: 2, name: "Bob", biography: "Dolar sit amet", "test2.jpg")
Friend(id: 3, name: "Dylan", biography: "Yes we can!", "test3.jpg")
ProfileViews(id: 1, views: 23)
ProfileViews(id: 3, views: 12)
I then want to order the [Friend]
array based on the views, so id 1, id 3, id 2. How can I do this? I am aware of the sorted(by:)
function but I seem only be able to do this within the [Friend]
array. I want to use the variables from the other struct however.
Upvotes: 1
Views: 89
Reputation: 93161
Here's another solution with some complexity analysis. Assuming n = profileViews.count
and m = friends.count
// Count the total views each id has. We use a dictionary
// because dictionary lookup is O(n) compared to an array
// search, which is O(n).
// Complexity: 2 * O(n)
let totalViews = Dictionary(grouping: profileViews, by: { $0.id })
.mapValues { pv in pv.reduce(0, {$0 + $1.views}) }
// Complexity: O(m log m)
let sortedFriends = friends.sorted { f0, f1 in
let view0 = totalViews[f0.id] ?? 0 // O(1)
let view1 = totalViews[f1.id] ?? 0 // O(1)
return view0 > view1
}
Overral complexity: 2 * O(n) + O(m log m)
Upvotes: 1
Reputation: 51892
This solution maps Friend and ProfileView into a tuples before sorting them and then mapping them back.
Assuming two arrays, friends
and profiles
:
let sorted = friends.map { friend -> (Friend, Int) in return (friend, profiles.first(where! { $0.id == friend.id })?.views ?? -1) }
.sorted { t1, t2 in t1.1 > t2.1 }
.map { $0.0 }
Upvotes: 2
Reputation: 299325
You likely want to join this information together permanently (by making views
part of Friend
), but if you want to keep them separate, you can still join them as needed exactly as you'd do a database JOIN:
struct FriendViews {
let friend: Friend
let views: Int
}
let friends: [Friend] = ...
let views: [ProfileViews] = ...
let friendViews = friends.map { (friend) -> FriendViews in
let viewCount = views.first(where: { $0.id == 1 })?.views ?? 0
return FriendViews(friend: friend, views: viewCount)
}
With that, you just need to sort friendViews using standard sorting tools that you seem to understand.
Upvotes: 2
Reputation: 12109
The easiest solution would be to have a ProfileViews
value as a property to Friend
, or even to just have profile views as an Int
property on Friend
.
If that is not possible, then the easiest solution would be to create a dictionary of profile views and look them up on the fly.
// Create a dictionary that maps from user ids to ProfileViews:
let viewIndex = Dictionary(
profileViews.map { views in
// views.id will be the key, views will be the value
(views.id, views)
}
// If two views values exist with the same user ID, they will be added together
uniquingKeysWith: { views1, views2 -> ProfileViews in
ProfileViews(id: views1.id, views: views1.views + views2.views)
}
)
// Sort friends by looking up the views in the viewIndex
let sortedFriends = friends.sorted(by: { friend1, friend2 -> Bool in
(viewIndex[friend1.id]?.views ?? 0) < (viewIndex[friend2.id]?.views ?? 0)
})
Upvotes: 2