4

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.

the Tin Man
  • 158,662
  • 42
  • 215
  • 303
Donny
  • 678
  • 11
  • 34
  • 1
    _"elements of B that exists in array A"_ – what about elements that don't exist in A? – Stefan Jun 23 '16 at 12:30
  • 1
    Possible duplicate of [Sort an array according to the elements of another array](http://stackoverflow.com/questions/11961685/sort-an-array-according-to-the-elements-of-another-array) – Wand Maker Jun 23 '16 at 16:53
  • I have placed my comment regarding possible duplicate @WandMaker – Donny Jun 23 '16 at 23:44

4 Answers4

23

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 }
Aleksei Matiushkin
  • 119,336
  • 10
  • 100
  • 160
2

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) }
radubogdan
  • 2,744
  • 1
  • 19
  • 27
2

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.

Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
1

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

B = B.sort_by { |x| A.index(x) }
tdy
  • 36,675
  • 19
  • 86
  • 83