dgilperez
dgilperez

Reputation: 10796

Sorting an Array of objects given an ordered list of ids

I have a collection of objects @users, each having its id attribute.

@users = [#<User id:1>, #<User id:2>]

I also have an ordered Array of ids.

ids = [2,1]

¿Is there a magical way to sort the collection using that list of ids? Without making another call to the database, if possible.

Thank you !!!

Upvotes: 10

Views: 4172

Answers (4)

tokland
tokland

Reputation: 67900

In fact you don't need to sort, build an intermediate indexed hash, it's O(n):

users_by_id = Hash[@users.map { |u| [u.id, u] }]
users_by_id.values_at(*ids)

If you still wanted to try a sort approach, the Schwartzian transform would be adequate:

@users.sort_by { |u| ids.index(u.id) }

However, using index within a loop is a red flag: O(n^2) time. We can build an intermediate hash to go back to O(n*log n):

indexes = Hash[ids.each_with_index.to_a]
@users.sort_by { |u| indexes[u.id] }

Upvotes: 21

Joe G
Joe G

Reputation: 104

If you can access the id of each user by calling user.id, you could sort the array like this:

@users.sort!{|a,b| a.id <=> b.id }

If you only have the ids in a separate array from the objects you could do the following: Zip the two arrays together, sort the resulting array on the ids, then collect the sorted users from the result.

users_ids = @users.zip(ids) # creates an array of smaller arrays each holding [user, id]
users_ids.sort!{|a,b| a[1] <=> b[1]} # sorts on the id in each sub-array
sorted_users = users_ids.collect{|item| item[0]} #grabs the users, leaving the ids behind

Take a glance at this: http://ariejan.net/2007/01/28/ruby-sort-an-array-of-objects-by-an-attribute

Upvotes: 0

Dave Newton
Dave Newton

Reputation: 160321

You certainly don't need to go to the DB since you already have the User objects, although since the users are in an array, you'd probably want to create a temporary map of id => User to get the end result.

Upvotes: 1

peakxu
peakxu

Reputation: 6675

Try this. First, build up a reverse mapping from id -> user.

ids_users = {}

@users.each {|user| ids_users[user.id] = user}

Then, use the ids order

ids.collect{ |id| ids_users[id] }

Upvotes: 12

Related Questions