How to sort an array of hashes in ruby - arrays

I have an array, each of whose elements is a hash with three key/value pairs:
:phone => "2130001111", :zip => "12345", :city => "sometown"
I'd like to sort the data by zip so all the phones in the same area are together. Does Ruby have an easy way to do that? Can will_paginate paginate data in an array?

Simples:
array_of_hashes.sort_by { |hsh| hsh[:zip] }
Note:
When using sort_by you need to assign the result to a new variable: array_of_hashes = array_of_hashes.sort_by{} otherwise you can use the "bang" method to modify in place: array_of_hashes.sort_by!{}

sorted = dataarray.sort {|a,b| a[:zip] <=> b[:zip]}

Use the bang to modify in place the array:
array_of_hashes.sort_by!(&:zip)
Or re-assign it:
array_of_hashes = array_of_hashes.sort_by(&:zip)
Note that sort_by method will sort by ascending order.
If you need to sort with descending order you could do something like this:
array_of_hashes.sort_by!(&:zip).reverse!
or
array_of_hashes = array_of_hashes.sort_by(&:zip).reverse

If you have Nested Hash (Hash inside a hash format) as Array elements (a structure like the following) and want to sort it by key (date here)
data = [
{
"2018-11-13": {
"avg_score": 4,
"avg_duration": 29.24
}
},
{
"2017-03-13": {
"avg_score": 4,
"avg_duration": 40.24
}
},
{
"2018-03-13": {
"avg_score": 4,
"avg_duration": 39.24
}
}
]
Use Array 'sort_by' method as
data.sort_by { |element| element.keys.first }

If you want to paginate for data in array you should require 'will_paginate/array' in your controller

Related

Ruby on Rails - Ruby, How to add the values from two hashes with same key, without overwriting the values?

first of all thank you for helping me with my SQL question, at this point.
Now I'm struggling with another thing, makes me think if I should quit being a programmer to be honest.
Anyway, my problem is : I have an array of hashes( and inside that hash another ) like this:
[
{"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}},
{"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}},
{"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}},
...
]
What I'm trying to accomplish is that, if there are two values with the same key, ie "A1" add those values into one whole hash, without overwriting the old values and having the month as a key desired output:
[
{"A1"=> { 1 => { :balance=> "0.0000", :price=>"9.0000"} },
{ 7 => { :balance => "34030.0000", :price => "34030.0000" } }},
and so on...
]
Is this posible?
Due the current format of the data you have, you'll need more than a couple of transformations. Most of them based on transforming the values of the resulting hash, after grouping the hashes in the array by their only key:
data
.group_by { |hash| hash.keys.first } # (1)
.transform_values { |value| value.flat_map(&:values) } # (2)
.transform_values { |values| values.index_by { |value| value[:month] } } # (3)
The first transformation is to group the current object, holding an array of hashes, by its only hash key, hence the keys.first, resulting in:
{
"A1"=>[
{"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}},
{"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}}
],
"A3"=>[{"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}]
}
The second, is to extract only the values from each hash, in the resulting hash, with arrays of hashes:
{
"A1"=>[
{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"},
{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}
],
"A3"=>[{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}]
}
Then, it just lacks to transform the array of hashes, to simply an hash, whose keys are the value of month:
{
"A1"=>{
1.0=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"},
7.0=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}
},
"A3"=>{4.0=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}
}
#Sebastian's answer is excellent. For variety, let's also consider an iterative approach. Not sure if it's more efficient or easier to understand, but it's always good to understand multiple perspectives.
Setting up the input data you gave us:
arr = [
{"A1"=>{:month=>1.0, :balance=>"0.0000", :price=>"9.0000"}},
{"A1"=>{:month=>7.0, :balance=>"34030.0000", :price=>"34030.0000"}},
{"A3"=>{:month=>4.0, :balance=>"34030.0000", :price=>"34030.0000"}}]
We create a new empty hash for our results.
new_hash = {}
And now iterating over the original data. We're going to make some assumptions about the form of the data.
# We know each thing is going to be a hash.
arr.each do |hsh|
# Set up some convenient variables for keys and
# values we'll need later.
key = hsh.keys.first
value = hsh.values.first
month = value[:month]
# If the output hash doesn't yet have the key,
# give it the key and assign an empty hash to it.
new_hash[key] ||= {}
# Assign the value to the hash, keyed to the current month.
new_hash[key][month] = value
# ... and get rid of the month key that's now redundant.
new_hash[key][month].delete(:month)
end
And the result is:
{"A1"=>{1.0=>{:balance=>"0.0000", :price=>"9.0000"},
7.0=>{:balance=>"34030.0000", :price=>"34030.0000"}},
"A3"=>{4.0=>{:balance=>"34030.0000", :price=>"34030.0000"}}}
Arguably it would be more useful for the desired return value to be a hash:
h = {"A1"=>{1=>{:balance=> "0.0000", :price=> "9.0000"},
7=>{:balance=>"34030.0000", :price=>"34030.0000"}},
"A3"=>{4=>{:balance=>"34030.0000", :price=>"34030.0000"}}}
That way you could write, for example:
require 'bigdecimal'
BigDecimal(h['A1'][7][:price])
#=> 0.3403e5
See BigDecimal. BigDecimal is generally used in financial calculations because it avoids round-off errors.
This result can be obtained by changing the values of :month to integers in arr:
arr = [
{"A1"=>{:month=>1, :balance=> "0.0000", :price=> "9.0000"}},
{"A1"=>{:month=>7, :balance=>"34030.0000", :price=>"34030.0000"}},
{"A3"=>{:month=>4, :balance=>"34030.0000", :price=>"34030.0000"}}
]
and by computing:
h = arr.each_with_object({}) do |g,h|
k,v = g.flatten
(h[k] ||= {}).update(v[:month]=>v.reject { |k,_| k == :month })
end
See Hash#flatten, Hash#update (aka merge!) and Hash#reject.
One could alternatively write:
h = arr.each_with_object(Hash.new { |h,k| h[k] = {} }) do |g,h|
k,v = g.flatten
h[k].update(v[:month]=>v.reject { |k,_| k == :month })
end
See the form of Hash::new that takes a block.

Split a list into two based on the filters in swift

Having a list of countries name, and if I search for a country that starts with B, how do I split the search with all the countries that begin with B as a list and the remaining countries as another list and then combine the both so that the countries with "B" is at the top
You can use sorted(by:) to achieve your goals. You simply need to check if the current and the next element start with your search term using the prefix operator and in case the current element does start with it, but the next one doesn't, sort the current element before the next one, otherwise preserve current order.
extension Array where Element == String {
/// Sorts the array based on whether the elements start with `input` or not
func searchSort(input: Element) -> Array {
sorted(by: { current, next in current.hasPrefix(input) && !next.hasPrefix(input) })
}
}
["Austria", "Belgium", "Brazil", "Denmark", "Belarus"].searchSort(input: "B")
Output: ["Belgium", "Brazil", "Belarus", "Austria", "Denmark"]
Do you need it for a searchBar? If yes, try this:
let filteredCountries = CountriesArray.filter({ (country: String) -> Bool in
return country.lowercased().contains(searchText.lowercased()) && country.hasPrefix(searchText)
})
Also, for sorting try this:
let sortedArray = filteredCountries.sorted(by: { $0.lowercased() < $1.lowercased() })
Even if you don't need it for a searchBar, explore filter() method, its very helpfull and easy to use.
You can also use Dictionary for splitting, it won't sort the key, but you got separated arrays to use:
let a = ["FF0000", "000000", "0083FF", "FBE58D", "7A9EB0", "909CE1"]
let b = Dictionary(grouping: a, by: { $0.first! })
print(b) //["0": ["000000", "0083FF"], "7": ["7A9EB0"], "F": ["FF0000", "FBE58D"], "9": ["909CE1"]]

How to convert an array of ids to array of hash with a key in ruby?

I have an array,
array = [1,2,3]
Need to transform it to:
newArray = [{id: 1}, {id: 2}, {id: 3}]
I know this, Is there any efficient way?
array.each { |id| newArray << { id: id } }
Anything like this?
array.map { |id| Hash[:id, id] }
the same with hash literal
array.map { |id| { id: id } }
A very elegant way:
convert to string
build the JSON style object
convert to array (inside the string)
let JSON parse it, for more (thread) safety
symbolize the keys, because in ruby it's preferred to use keys rather than strings
additionally you could use Base58 random key and hash it twice to mitigate timing attacks
JSON.parse([1,2,3].map(&:to_s).collect{ "{\"id\": \"#{_1}\"}" }.join(",").prepend("[").concat("]")).collect(&:symbolize_keys)
=> [{:id=>"1"}, {:id=>"2"}, {:id=>"3"}]

How to iterate through an array of hashes sorting the keys in Ruby

I have an array of hashes like this:
items = [{"id"=>"123", "code"=>"abc","name"=>"test", "type"=>["good"]},
{"id"=>"555", "code"=>"ddd","name"=>"foo", "type"=>["better"]},
{"id"=>"098", "code"=>"zyx","name"=>"bar", "type"=>["best"]}]
I am trying to sort each hash within the array by the key.
I tried this:
items.each { |item| item = item.sort.to_h }
It returns the same result:
[{"id"=>"123", "code"=>"abc", "name"=>"test", "type"=>["good"]},
{"id"=>"555", "code"=>"ddd", "name"=>"foo", "type"=>["better"]},
{"id"=>"098", "code"=>"zyx", "name"=>"bar", "type"=>["best"]}]
but when I try this:
items[0].sort.to_h
this is the result:
{"code"=>"abc", "id"=>"123", "name"=>"test", "type"=>["good"]}
So it looks like when I call the individual elements within items using items[x] where x is an index value within the array, it sort it.
But I need a solution to iterate through each element doing that and saving the sort.
Any thoughts?
I solved it with this:
items.map { |item| item.sort.to_h }
Thanks #SagarPandya and #Dark
If, as in the example, all the hashes have the same keys, it would be faster to perform a single sort on the keys.
sorted_keys = items.first.keys.sort
#=> ["code", "id", "name", "type"]
items.map { |item| sorted_keys.each_with_object({}) { |k,h| h[k] = item[k] } }
#=> [{"code"=>"abc", "id"=>"123", "name"=>"test", "type"=>["good"]},
# {"code"=>"ddd", "id"=>"555", "name"=>"foo", "type"=>["better"]},
# {"code"=>"zyx", "id"=>"098", "name"=>"bar", "type"=>["best"]}]
The last line could alternatively be written
items.map { |item| sorted_keys.zip(item.values_at(*sorted_keys)).to_h }

How to get first n values from perl Hash of arrays

Experts,
I have a hash of array in perl which I want to print the first 2 values.
my %dramatis_personae = (
humans => [ 'hamnet', 'shakespeare', 'robyn', ],
faeries => [ 'oberon', 'titania', 'puck', ],
other => [ 'morpheus, lord of dreams' ],
);
foreach my $group (keys %dramatis_personae) {
foreach (#{$dramatis_personae{$group}}[0..1]) { print "\t$_\n";}
}
The output I get is
"hamnet
shakespeare
oberon
titania
morpheus
lord of dreams"
which is basically first two array values for each key. But I am looking to have the output as:
hamnet
shakespeare
Please advise how I can get this result. Thanks!
Keys of hashes are not ordered, so you should specify keys ordering by yourself. Then you can concatenate arrays from each key specified and take first two values from resulting array, is it what you want ?
print "\t$_\n" foreach (map {(#{$dramatis_personae{$_}})} qw/humans faeries other/)[0..1];
Hashes are unordered, so what you requested to achieve is impossible. Unless you have some knowledge about the keys and the order they should be in, the closest you can get is something that can produce any of the following:
'hamnet', 'shakespeare'
'oberon', 'titania'
'morpheus, lord of dreams', 'hamnet'
'morpheus, lord of dreams', 'oberon'
The following is an implementation that does just that:
my $to_fetch = 2;
my #fetched = ( map #$_, values %dramatis_personae )[0..$to_fetch-1];
The following is a more efficient version for larger structures. It also handles insufficient data better:
my $to_fetch = 2;
my #fetched;
for my $group (values(%dramatis_personae)) {
if (#$group > $to_fetch) {
push #fetched, #$group[0..$to_fetch-1];
$to_fetch = 0;
last;
} else {
push #fetched, #$group;
$to_fetch -= #$group;
}
}
die("Insufficient data\n") if $to_fetch;

Resources