1

If I have a an array of objects like:

[{id: 1, name: 'Apple'},
  {id: 2, name: 'Orange'},
  {id: 3, name: 'Banana'}]

And I have an array of ids: [3, 1, 2]

Does Ruby have a concise and/or efficient way to sort the array by the ids in the second array like:

[{id: 3, name: 'Banana'},
  {id: 1, name: 'Apple'},
  {id: 2, name: 'Orange'}]
9
  • @sawa: Editing just to change the style? I like it sparse... Commented Sep 14, 2014 at 2:38
  • @BorisStitnicky You edited just to change the style. I reverted some unnecessary changes you made. Please don't change the OP's style, especially from a well style to a non-educated person's style. An educated person would not put spaces inside parentheses, braces, or brackets (it is fine to have line endings). Commented Sep 14, 2014 at 3:49
  • @BorisStitnicky Continued: (it is fine to have line endings or indentation). Sorry if it sounded offensive. I am not targeting particularly you. I know that many programmers do that, and I am annoyed by it. Commented Sep 14, 2014 at 3:55
  • @sawa, it's not a matter of being or not being educated. I have read many manuals of style, and I prefer readability over saving characters. I know that character spaces on an 80 character line are precious, but my brain simply doesn't read it so quickly when the parens are crammed together. So it's a matter of personal preference or personal abilities. If you can read crammed parens efficiently, congrats to you. I won't wage a revert war with you. Commented Sep 14, 2014 at 4:08
  • @BorisStitnicky I am not sure if you got it correctly. It is not a matter of saving characters. It is a matter of readability. These characters are designed to surround something, and they are designed to look beautiful when put right next to their content without spaces in between. Moreover, no Manual of Style book will tell you to put spaces inside them. And following these rules is what an educated person is expected to do. You and I agree that it should be for readability/beauty, but we seem to have opposite senses. Commented Sep 14, 2014 at 4:29

2 Answers 2

3

It can be done like this:

a1 = [{id: 1, name: 'Apple'}, {id: 2, name: 'Orange'}, {id: 3, name: 'Banana'}]
a2 = [3,1,2]

a1.sort_by{|h| a2.index(h[:id])}
Sign up to request clarification or add additional context in comments.

Comments

2

Two other ways:

#1

def order_hashes1(a,order)
  a.each_with_object({}) { |h,g| g.update({h[:id]=>h}) }.values_at(*order)
end

order_hashes1(a1,a2)
  #=> [{:id=>3, :name=>"Banana"},
  #    {:id=>1, :name=>"Apple"},
  #    {:id=>2, :name=>"Orange"}]

#2

def order_hashes2(a,order)
  order.map { |i| a.find { |h| h[:id] == i } }
end

order_hashes2(a1,a2)
  #=> [{:id=>3, :name=>"Banana"},
  #    {:id=>1, :name=>"Apple"},
  #    {:id=>2, :name=>"Orange"}]

Benchmark

Methods compared

module Methods
  def sawa(a,order)
    a.sort_by{ |h| order.index(h[:id]) }
  end

  def order_hashes1(a,order)
    a.each_with_object({}) { |h,g| g.update({h[:id]=>h}) }.values_at(*order)
  end

  def order_hashes2(a,order)
    order.map { |i| a.find { |h| h[:id] == i } }
  end
end

include Methods
methods = Methods.instance_methods(false)
  #=> [:order_hashes1, :order_hashes2, :sawa]

Test data

def test_data(n)
  a1 = n.times.with_object([]) { |i,a| a << { id: i, name: 'Apple' } }.shuffle
  a2 = n.times.to_a.shuffle
  [a1, a2]
end

Confirm all methods return the same values

a1, a2 = test_data(1_000)
result = send(method.first, a1, a2)
puts methods[1..-1].all? { |m| result = send(m,a1,a2) }
  #=> true

The benchmark routine

require 'benchmark'

a1, a2 = test_data(20_000)

Benchmark.bm(methods.map { |m| m.to_s.size }.max) do |bm|
  methods.each do |m|
    bm.report m.to_s do
      send(m, a1, a2)
    end
  end
end    
                    user     system      total        real
order_hashes1   0.030000   0.000000   0.030000 (  0.033169)
order_hashes2  49.300000   0.110000  49.410000 ( 49.486159)
sawa            1.500000   0.000000   1.500000 (  1.499078)

Epilogue

I was not surprised that order_hashes2 never got out of the gate, but I was startled that building the hash and then extracting values with values_at was so much faster than @sawa's solution. I expect the latter spent most of its time performing the index operation.

Reader challenger: there are many other ways to address this problem. Let's see your suggestions and I'll add them to the benchmark.

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.