简体   繁体   中英

Ruby - Sort a hash by order of an array

I have an array and a hash and what I want to achieve is to sort the hash (with each id inside each hash) based on the sort that is in the array and if the id (in hash ) doesn't exist in my_array , they should not be deleted and just be pushed down in the sort.

my_array = [4, 2, 5, 3, 1]
hash = [
  {"id" => 1, "field_name" => "foo"},
  {"id" => 2, "field_name" => "bar"},
  {"id" => 3, "field_name" => "abc"},
  {"id" => 4, "field_name" => "zsh"},
  {"id" => 5, "field_name" => "kql"},
  {"id" => 6, "field_name" => "plo"},
  {"id" => 7, "field_name" => "cde"}
]

Needed output

[
  {"id" => 4, "field_name" => "zsh"},
  {"id" => 2, "field_name" => "bar"},
  {"id" => 5, "field_name" => "kql"},
  {"id" => 3, "field_name" => "abc"},
  {"id" => 1, "field_name" => "foo"},
  {"id" => 6, "field_name" => "plo"},
  {"id" => 7, "field_name" => "cde"}
]

Appreciate any help and thanks in advance!

Map Over Your ID Array to Select Matching Hashes in Order

While there may be a more elegant way to do this, I think the solution below is quite clear. It uses your Array of id elements as an Enumerable object to map over, returning the matching Hash object with a matching value for each "id" key encountered during iteration (if found), and then removes any elements where no match was found, eg the Array returned by #map returns nil .

Using Ruby 3.1.2:

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

array_of_hashes = [
  {"id" => 1, "field_name" => "foo"},
  {"id" => 2, "field_name" => "bar"},
  {"id" => 3, "field_name" => "abc"},
  {"id" => 4, "field_name" => "zsh"},
  {"id" => 5, "field_name" => "kql"},
  {"id" => 6, "field_name" => "plo"},
  {"id" => 7, "field_name" => "cde"}
]

id_order.map { |id| array_of_hashes.detect { |h| h["id"] == id } }.compact

This correctly returns your ordered results as specified in the first Array:

#=> 
[{"id"=>4, "field_name"=>"zsh"},                                                                                         
 {"id"=>2, "field_name"=>"bar"},                                                                                         
 {"id"=>5, "field_name"=>"kql"},                                                                                         
 {"id"=>3, "field_name"=>"abc"},                                                                                         
 {"id"=>1, "field_name"=>"foo"}]                                                                                         

Note that it doesn't return Hash objects for IDs 6 or 7 because they aren't present in your Array of IDs to search for. You can adjust the #map to treat those as pass-throughs (although you haven't explained how or why they should appear at the end of your resulting Array) or add them to the Array of elements to search for, which would be the preferred approach. However, since they weren't addressed in your original post, this is in fact the correct output if no matching ID is found.

my_array = [4, 7, 5, 3, 1]
arr = [
  {"id" => 1, "field_name" => "foo"},
  {"id" => 2, "field_name" => "bar"},
  {"id" => 3, "field_name" => "abc"},
  {"id" => 4, "field_name" => "zsh"},
  {"id" => 5, "field_name" => "kql"},
  {"id" => 6, "field_name" => "plo"},
  {"id" => 7, "field_name" => "cde"}
]

Note that I've changed my_array from that given in the question and have renamed the array to something more appropriate.

arr.sort_by.with_index do |h,i|
  my_array.include?(h["id"]) ? [0, h["id"]] : [1,i]
end
  #=> [{"id"=>1, "field_name"=>"foo"},
  #    {"id"=>3, "field_name"=>"abc"},
  #    {"id"=>4, "field_name"=>"zsh"},
  #    {"id"=>5, "field_name"=>"kql"},
  #    {"id"=>7, "field_name"=>"cde"},
  #    {"id"=>2, "field_name"=>"bar"},
  #    {"id"=>6, "field_name"=>"plo"}]

See Enumerable#sort_by . That method uses Array#<=> to order elements. See especially the third paragraph at the doc for <=> .

Below I've listed the arrays used by sort_by for each element of arr .

{"id" => 1, "field_name" => "foo"}  ->  [0, 1]
{"id" => 2, "field_name" => "bar"}  ->  [1, 1]
{"id" => 3, "field_name" => "abc"}  ->  [0, 3]   
{"id" => 4, "field_name" => "zsh"}  ->  [0, 4]
{"id" => 5, "field_name" => "kql"}  ->  [0, 5]
{"id" => 6, "field_name" => "plo"}  ->  [1, 5]
{"id" => 7, "field_name" => "cde"}  ->  [0, 7]

If [type, x] is the array sort_by uses for sorting, it will put all elements for which type 0 before those for which type = 1 . To break ties it orders elements by x . For type = 1 , x is the index of the element, which keeps them in order at the end.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM