PennyWise
PennyWise

Reputation: 647

How to sort two structs with one common variable

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

Answers (4)

Code Different
Code Different

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

Joakim Danielson
Joakim Danielson

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

Rob Napier
Rob Napier

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

Palle
Palle

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

Related Questions