45

I have an array of ids

a1 = [1, 2, 3, 4, 5]  

and I have another array of objects with ids in random order

a2 = [(obj_with_id_5), (obj_with_id_2), (obj_with_id_1), (obj_with_id_3), (obj_with_id_4)]  

Now I need to sort a2 according to the order of ids in a1. So a2 should now become:

[(obj_with_id_1), (id_2), (id_3), (id_4), (id_5)]  

a1 might be [3, 2, 5, 4, 1] or in any order but a2 should correspond to the order of ids in a1.

I do like this:

a1.each_with_index do |id, idx|
  found_idx = a1.find_index { |c| c.id == id }
  replace_elem = a2[found_idx]
  a2[found_idx] = a2[idx]
  a2[idx] = replace_elem
end  

But this still might run into an O(n^2) time if order of elements of a2 is exactly reverse of a1. Can someone please tell me the most efficient way of sorting a2?

4 Answers 4

88

I'll be surprised if anything is much faster than the obvious way:

a2.sort_by{|x| a1.index x.id}
Sign up to request clarification or add additional context in comments.

8 Comments

assuming a1 is sorted (and it is from the problem stmt) and the container you're using for a1 can take advantage of the fact that a1 is sorted then I agree this would be faster than O(n^2).
No a1 being sorted is not an advantage, I'm not sure why you would think that. This way is fast because it's built-in. Trying to beat built-in sort_by seems a waste of time to me.
a1 being sorted is an advantage. If it is sorted then the index operation should run in O(log n) time (assuming binary search) and if it is not sorted the index will run in O(n) time.
This method is blazing fast too but using hashes is like travelling faster than the speed of light. I ran a test with both methods for 10,000 numbers (just for the sake of testing). Your method took 1.3secs on an avg but with hashes it took 0.009secs on avg..
-1 for this method. For Example: x - Array of 1000 elements, not sorted x2 - Array of same elements, sorted Benchmark.bm { |t| t.report('test1') { x.index_by { |c| c }.values_at(*x2).compact } t.report('test2') { x.sort_by { |v| x2.index v } } } test1 real: 0.000709 test2 real: 0.048563
|
27
hash_object = objects.each_with_object({}) do |obj, hash| 
  hash[obj.object_id] = obj
end

[1, 2, 3, 4, 5].map { |index| hash_object[index] }
#=> array of objects in id's order

I believe that the run time will be O(n)

7 Comments

I believe this would be O(n^2). the actual sort is O(n), but the preparation step would make it n^2
I'm not agree, to build hash table require O(n), look here en.wikipedia.org/wiki/Hash_table
Yes, building the hash table is O(n) time. And the sort is O(n) time. So you have 2xO(n)... hmmm... that would be less than n^2. I stand corrected. good catch!
The first step seems the same as using hash_object = objects.index_by(&:object_id)
@kamal: It's O(n), but doesn't do what's asked -- it'll return [nil, nil, nil, nil, nil] unless the object_ids happen to be the numbers 1 through 5. To make it work, you need to get the object_ids and sort them, which won't be any better than objects.index_by(&:object_id). Also, it isn't necessary to explain the O(n) claim here, but note that the O(n log n) lower bound only applies to comparison sorts.
|
20

I like the accepted answer, but in ActiveSupport there is index_by which makes creating the initial hash even easier. See Cleanest way to create a Hash from an Array

In fact you could do this in one line since Enumerable supports index_by as well:

a2.index_by(&:id).values_at(*a1)

1 Comment

This only works if you don't have any duplicates in your original list. Index by will overwrite any duplicate ids. This may or may not be an issue for you.
7

Inspired by Eric Woodruff's Answer, I came up with the following vanilla Ruby solution:

a2.group_by(&:object_id).values_at(*a1).flatten(1)

Method documentation:

1 Comment

I like this solution best. It's efficient (and I suspect more efficient than @pguardiario's solution) and, importantly, permits two elements of a2 to have the same value for "id". The question does not state that the id's are unique, yet some answers, including the one selected, depend on the id's being unique.

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.