7

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.

3
  • 1
    "elements of B that exists in array A" – what about elements that don't exist in A? Commented Jun 23, 2016 at 12:30
  • 1
    Possible duplicate of Sort an array according to the elements of another array Commented Jun 23, 2016 at 16:53
  • I have placed my comment regarding possible duplicate @WandMaker Commented Jun 23, 2016 at 23:44

4 Answers 4

27

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

6 Comments

Looks like there is no need to make the check that I did in my answer :) Good answer.
Although it fails for elements in B that are not in A. Try to delete element 3 from A
Works perfect ! Thanks
Works until you don't have elements from B in A.
@radubogdan Good catch, updated. I do not think that your decision to drop inexisting in A elements is OK.
|
3

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

Comments

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.

2 Comments

If every element of B is in A, this solution is definitely the most elegant.
@mudasobwa, I made some changes that may interest you.
1

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

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

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.