Reputation: 37
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!
Upvotes: 1
Views: 191
Reputation: 110675
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.
Upvotes: 2
Reputation: 84343
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, e.g. 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.
Upvotes: 2