10

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 !!!

4 Answers 4

21

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] }
Sign up to request clarification or add additional context in comments.

3 Comments

Awesome one-liner solution. Thanks for the link to Schwartzian transform.
How about the situation when some items may repeat ? For example the list of last viewed files.
@Artur79: Then you probably need Enumerable#group_by
12

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] }

1 Comment

Alternative way to construct the hash: id_users = Hash[ @users.map { |u| [u.id, u] } ]
1

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.

Comments

0

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.