I have an array of hashes which I need to sort based on two different key value pairs.
Here is the array I am trying to sort:
array_group = [
{operator: OR, name: "some string", status: false},
{operator: AND, name: "other string", status: false},
{operator: _NOT_PRESENT, name: "another string", status: true},
{operator: AND, name: "just string", status: true}
]
I want to sort array_group
so I have items with status: true
first, followed by status: false
, followed by the items with operator: _NOT_PRESENT
and finally sort it based on name, resulting in something like:
array_group = [
{operator: AND, name: "just string", status: true},
{operator: AND, name: "other string", status: false},
{operator: OR, name: "some string", status: false},
{operator: _NOT_PRESENT, name: "another string", status: true},
]
Is there a way that I can get this done without creating sub-arrays and sorting them and concatenating them back?
Perl's built in sort function allows us to specify a custom sort order. Within the curly braces Perl gives us 2 variables, $a and $b, which reference 2 items to compare. In our case, these are hash references so we can access the elements of the hash and sort by any key we want.
##Hash#keys and Hash#values A simple way to sort a hash keys or values is to use the methods Hash#keys and Hash#values. Those 2 methods are simply returning an array of the keys or the values of your hash. So as soon as you get an array with only one of the type of values you want to work with, it's all good!
You can use the sort method on an array, hash, or another Enumerable object & you'll get the default sorting behavior (sort based on <=> operator) You can use sort with a block, and two block arguments, to define how one object is different than another (block should return 1, 0, or -1)
You can also use Enumerable#sort_by. The example builds an array which is compared element by element when sorting.
array_group.sort_by { |e| [e[:operator] == "_NOT_PRESENT" ? 1 : 0,
e[:status] ? 0 : 1,
e[:name]] }
The example above orders records with operator: "_NOT_PRESENT"
also by :status
. The following snippet precisely performs the ordering from the question.
def priority(h)
case
when h[:operator] == "_NOT_PRESENT" then 3
when h[:status] == false then 2
# h[:status] == true
else 1
end
end
array_group.sort_by { |e| [priority(e), e[:name]] }
You can use the Array.sort
method. It accepts a block with two arguments (x, y), when x is larger than y it should return 1, otherwise -1, and 0 if they are equal.
The code:
OR = "OR"
AND = "AND"
_NOT_PRESENT = "_NOT_PRESENT"
array_group = [
{operator: OR, name: "some string", status: false},
{operator: AND, name: "other string", status: true},
{operator: _NOT_PRESENT, name: "another string", status: true},
{operator: AND, name: "just string", status: true}
]
results = array_group.sort do |x, y|
next x[:operator] == _NOT_PRESENT ? 1 : -1 if x[:operator] == _NOT_PRESENT || y[:operator] == _NOT_PRESENT
next x[:status] ? -1 : 1 if x[:status] != y[:status]
next x[:name] <=> y[:name]
end
And btw, you your input and output data doesn't match each other — the hash with OR is false
in the input, but true
in the output.
I believe your output should look like:
[{:operator=>"AND", :name=>"just string", :status=>true},
{:operator=>"AND", :name=>"other string", :status=>true},
{:operator=>"OR", :name=>"some string", :status=>false},
{:operator=>"_NOT_PRESENT", :name=>"another string", :status=>true}]
That output will actually match your sorting logic.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With