How does ruby group, order and select in a single statement - ruby

arr = [{name: 'one'}, {name: 'two'}, {name: 'one'}, {name: 'three'}]
How can I get an array of string from it, ["one", "two", "three"]. If each item in arr is considered a row, what I want is a simple ruby way of doing what following sql statement does:
select name from test group by name order by count(*) desc
This is the way I do in Rails currently, but I think that is too much coding.
tags_hash = arr.inject(Hash.new(0)) {|h,t| h[t.name]+=1; h}
descending = -1
tags_array = tags_hash.sort_by {|k,v| v * descending}
#tags = tags_array.collect {|item| item[0]}

arr.group_by { |i| i[:name] }.sort_by { |name, items| items.length }.reverse.map(&:first)
Group by groups the elements by name:
arr.group_by { |i| i[:name] }
# => {"one"=>[{:name=>"one"}, {:name=>"one"}], "two"=>[{:name=>"two"}], "three"=>[{:name=>"three"}]}
Sort by orders them by the number of found items in each, reverse makes it descending order.
The map extracts the actual name of each group.
# => ["one", "two", "three"]

If you want to get ["one", "two", "three"] sorted from that array you can write something like this:
counts = Hash.new(0)
arr.map{|i| i[:name]}.each { |name| counts[name] += 1 }
counts.sort{|a,b| b[1] <=> a[1]}.map(&:first)

Related

Create a new hash from an existing array of hashes

I am newbie to ruby . I have an array of hashes input_array
[{
"name"=>"test1",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>1000,
"name"=>"test2",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>5000,
"name"=>"test3",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>3000,
"name"=>"test4",
"zone_status"=>"valid",
"certificate_status"=>"valid",
"users"=>2000}]
and an array
existing_names_array = ["test1","test2"]
The below line gets me all the names into input_names
input_array.each_with_index {|val, index| input_names << input_array[index]['name'] }
But how can I get the input_names to be a hash with name as key and its respective users as value?
Because my final goal is to check the names which are in input_names, but not in existing_names_array and get a count of those names and users
As the names tes1,test2 exists in existing_names_array, I need the count of rest of names and count of their respective users in input_array
Expected output:
output_names = test3, test 4
total_output_names = 2
output_users = 3000,2000
total_output_users = 5000
If you use ActiveSupport's Enumerable#index_by with Ruby core's Hash#transform_values it's pretty easy, if I'm understanding your question correctly:
# add gem 'activesupport' to Gemfile, or use Rails, then ..
require 'active_support/all'
users_and_counts = input_array.
index_by { |hsh| hsh["name"] }.
transform_values { |hsh| hsh["users"] }
# => { "test1" => 1000, "test2" => 5000, ... }
You can do this with Enumerable#reduce (or Enumerable#each_with_object) as well:
users_and_counts = input_array.reduce({}) do |memo, hsh|
memo[hsh["name"]] = hsh["users"]
memo
end
Or, the simplest way, with good old each:
users_and_counts = {}
input_array.each do |hsh|
users_and_counts[hsh["name"]] = hsh["users"]
end
in response to comment
In general, there are a few ways to check whether an element is found in an array:
array = ["foo", "bar"]
# 1. The simple standard way
array.include?("foo") # => true
# 2. More efficient way
require 'set'
set = Set.new(array)
set.member?("foo") # => true
So with this knowledge we can break up our task into a few steps:
Make a new hash which is a copy of the one we built above, but without the key-vals corresponding to users in the existing_names_array (see Hash#select and Hash#reject):
require 'set'
existing_names_set = Set.new(existing_names_array)
new_users_and_counts = users_and_counts.reject do |name, count|
existing_names_set.member?(name)
end
# => { "test3" => 3000, "test4" => 2000 }
Use Hash#keys to get the list of user names:
new_user_names = new_users_and_counts.keys
# => ["test3", "test4"]
Use Hash#values to get the list of counts:
new_user_counts = new_users_and_counts.values
# => [3000, 2000]
I assume input_array is to be as follows.
input_array = [
{ "name"=>"test1", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>1000 },
{ "name"=>"test2", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>5000 },
{ "name"=>"test3", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>3000 },
{ "name"=>"test4", "zone_status"=>"valid", "certificate_status"=>"valid",
"users"=>2000}
]
We are also given:
names = ["test1", "test2"]
The information of interest can be represented nicely by the following hash (which I will construct):
summary = { "test1"=>1000, "test2"=>5000 }
We might use that to compute the following.
names = summary.keys
#=> ["test1", "test2"]
users = summary.values
#=> [1000, 5000]
total_users = users.sum
#=> 6000
There are many ways to construct the hash summary. I prefer the following.
summary = input_array.each_with_object({}) do |g,h|
key = g["name"]
h[key] = g["users"] if names.include?(key)
end
#=> {"test1"=>1000, "test2"=>5000}
Another way is as follows.
summary = input_array.map { |g| g.values_at("name", "users") }.
.to_h
.slice(*names)
See [Hash#values_at(https://ruby-doc.org/core-2.7.0/Hash.html#method-i-values_at), Array#to_h and Hash#slice. The steps are as follows.
arr = input_array.map { |g| g.values_at("name", "users") }
#=> [["test1", 1000], ["test2", 5000], ["test3", 3000], ["test4", 2000]]
h = arr.to_h
#=> {"test1"=>1000, "test2"=>5000, "test3"=>3000, "test4"=>2000}
summary = h.slice(*names)
#=> {"test1"=>1000, "test2"=>5000}
The splat operator converts h.slice(*names), which is h.slice(*["test1", "test2"]), to h.slice("test1", "test2"). That's because Hash#slice was defined to accept a variable number of keys as arguments, rather than an array of keys as the argument.

Convert an array to hash, where keys are the indices

I am transforming an array into a hash, where the keys are the indices and values are the elements at that index.
Here is how I have done it
# initial stuff
arr = ["one", "two", "three", "four", "five"]
x = {}
# iterate and build hash as needed
arr.each_with_index {|v, i| x[i] = v}
# result
>>> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Is there a better (in any sense of the word "better") way to do it?
arr = ["one", "two", "three", "four", "five"]
x = Hash[(0...arr.size).zip arr]
# => {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Ruby < 2.1:
Hash[arr.map.with_index { |x, i| [i, x] }]
#=> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Ruby >= 2.1:
arr.map.with_index { |x, i| [i, x] }.to_h
x = Hash.new{|h, k| h[k] = arr[k]}
%w[one two three four five].map.with_index(1){ |*x| x.reverse }.to_h
Remove (1) if you want to start the index from 0.
Here is a solution making use of Object#tap, to add values to a newly-created hash:
arr = ["one", "two", "three", "four", "five"]
{}.tap do |hsh|
arr.each_with_index { |item, idx| hsh[idx] = item }
end
#=> {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Many good solutions already, just adding a variant (provided you do not have duplicated values):
["one", "two", "three", "four", "five"].map.with_index.to_h.invert
# => {0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
You could monkey patch Array to provide a new method:
class Array
def to_assoc offset = 0
# needs recent enough ruby version
map.with_index(offset).to_h.invert
end
end
Now you can do:
%w(one two three four).to_assoc(1)
# => {1=>"one", 2=>"two", 3=>"three", 4=>"four"}
This is a common operation I'm doing in Rails apps so I keep this monkey patch around in an initializer.
You should go with the map method, it's better and uses less memory. Another solution is with while loop. Good to know this way too, because often you may need to use the while loop.
At example 2, if you want to take each odd index in the an array as a key in the hash, and each even index as a value in the hash
Example 1:
some_array = ["one", "two", "three", "four", "five"]
def to_hash(arr)
counter = 0
new_hash = Hash.new(0)
while counter < arr.length
new_hash[counter] = arr[counter]
counter += 1
end
return new_hash
end
puts to_hash(arr)
# Output
{0=>"one", 1=>"two", 2=>"three", 3=>"four", 4=>"five"}
Example 2 -
Perhaps after getting data as a string, and you split this string to an array, and now you want to convert to key, value.
some_array = ['KBD', 'King Bedroom', 'QBD', 'Queen Bedroom', 'DBD', 'Double Bedroom', 'SGLB', 'Single Bedroom']
def to_hash(arr)
new_hash = Hash.new(0)
counter = 0
while counter < arr.length
new_hash[arr[counter]] = arr[counter+1]
counter += 2
end
return "#{new_hash}"
end
puts to_key_value_hash(some_array)
# Output
{"KBD"=>"King Bedroom", "QBD"=>"Queen Bedroom", "DBD"=>"Double Bedroom", "SGLB"=>"Single Bedroom"}
Many ways to do this, but just saying not to forget the while loop.

How to create an array out of certain values in hashes, which are contained in an array

I have an array of hashes:
a = [{name: "ben", sex: "m"},{name: "sarah", sex: "f"}]
What is the easiest way to create an array out of this with just the names? So I end up with:
b = ["ben", "sarah"]
I know you can do the following, but just wondering if there's a shortcut
b = []
a.each do |x|
b << x[:name]
end
Thanks for reading.
b = a.map { |hash| hash[:name] }
This is pretty basic Ruby, take a look at the Enumerable module and study all the methods carefully. [edit] Some random links about the topic: 1, 2, 3.

How to sort an array in Ruby to a particular order?

I want to sort an array in particular order given in another array.
EX: consider an array
a=["one", "two", "three"]
b=["two", "one", "three"]
Now I want to sort array 'a' in the order of 'b', i.e
a.each do |t|
# It should be in the order of 'b'
puts t
end
So the output should be
two
one
three
Any suggestions?
Array#sort_by is what you're after.
a.sort_by do |element|
b.index(element)
end
More scalable version in response to comment:
a=["one", "two", "three"]
b=["two", "one", "three"]
lookup = {}
b.each_with_index do |item, index|
lookup[item] = index
end
a.sort_by do |item|
lookup.fetch(item)
end
If b includes all elements of a and if elements are unique, then:
puts b & a
Assuming a is to be sorted with respect to order of elements in b
sorted_a =
a.sort do |e1, e2|
b.index(e1) <=> b.index(e2)
end
I normally use this to sort error messages in ActiveRecord in the order of appearance of fields on the form.

Populate ruby Array1 with Array2 String element if and only if Array2 element matches Hash value(not key)

I have a ruby hash:
VALS = { :one => "One", :two => "Two" }
and an Array:
array2 = ["hello", "world", "One"]
Question: How can I populate a new array1 so that it only pulls in any values in array2 that match exactly the values in VALS?
For example, I have tried:
array2.each_with_index do |e,i|
array1 << e if VALS[i] ~= e
end
Along with other thing, and none work. Noob.
Thanks
brilliant! but whent I tried:
p array.select { |i| hash.has_value? i ? array[i+1] : "foo"}
I got an can't convert fixnum error. I must be missing something.
Using nested loops would be very slow if both collections are large. It's better to treat the contents as sets:
array1 = VALS.values & array2
print array1
Output:
One
Here's an option:
hash = { :one => "One", :two => "Two" }
array = ["hello", "world", "One"]
p array.select { |i| hash.has_value? i }
# >> ["One"]
got it!
array.select do |i|
if VALS.has_value? i
result << array[array.index(i)+1]
end
end

Resources