Donny
Donny

Reputation: 718

How to sort one array based on another array using Ruby

There are two arrays:

A = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] 
B = [3, 4, 1, 5, 2, 6]

I want to sort B in a way that for all the elements of B that exists in A, sort the elements in the order that is in array A.

The desired sorted resulted would be

B #=> [1, 2, 3, 4, 5, 6]

I have tried to do

B = B.sort_by { |x| A.index }

but it does not work.

This question differs from the possible duplicates because it deals with presence of elements in the corresponding array and no hashes are present here.

Upvotes: 7

Views: 7175

Answers (4)

Mradul Singh
Mradul Singh

Reputation: 11

You just missed x in A.index, so the query should be:

B = B.sort_by { |x| A.index(x) }

Upvotes: 1

Cary Swoveland
Cary Swoveland

Reputation: 110675

First consider the case where every element of B is in A, as with the question's example:

A = [1,2,3,4,5,6,7,8,9,10]
B = [3,6,1,5,1,2,1,6]

One could write the following, which requires only a single pass through A (to construct g1) and a single pass through B.

g = A.each_with_object({}) { |n,h| h[n] = 1 }
  #=> {1=>1, 2=>1, 3=>1, 4=>1, 5=>1, 6=>1, 7=>1, 8=>1, 9=>1, 10=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
  #=> [1, 1, 1, 2, 3, 5, 6, 6]

If there is no guarantee that all elements of B are in A, and any that are not are to be placed at the end of the sorted array, one could change the calculation of g slightly.

g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }

This requires one more pass through A and through B.

Suppose, for example,

A = [2,3,4,6,7,8,9]

and B is unchanged. Then,

g = (A + (B-A)).each_with_object({}) { |n,h| h[n] = 1 }
  #=> {2=>1, 3=>1, 4=>1, 6=>1, 7=>1, 8=>1, 9=>1, 1=>1, 5=>1}
B.each_with_object(g) { |n,h| h[n] += 1 }.flat_map { |k,v| [k]*(v-1) }
  #=> [2, 3, 6, 6, 1, 1, 1, 5]

This solution demonstrates the value of a controversial change to hash properties that were made in Ruby v1.9: hashes would thereafter be guaranteed to maintain key-insertion order.

1 I expect one could write g = A.product([1]).to_h, but the doc Array#to_h does not guarantee that the keys in the hash returned will have the same order as they do in A.

Upvotes: 2

Aleksei Matiushkin
Aleksei Matiushkin

Reputation: 121000

It perfectly works:

▶ A = [1,3,2,6,4,5,7,8,9,10]
▶ B = [3,4,1,5,2,6]
▶ B.sort_by &A.method(:index)
#⇒ [1, 3, 2, 6, 4, 5]

If there could be elements in B that are not present in A, use this:

▶ B.sort_by { |e| A.index(e) || Float::INFINITY }

Upvotes: 27

radubogdan
radubogdan

Reputation: 2834

I would start by checking what elements from B exist in A :

B & A

and then sort it:

(B & A).sort_by { |e| A.index(e) }

Upvotes: 3

Related Questions