Count how many times an element appears in an array in Ruby - arrays

This is the array that is going to be processed:
[{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
and I would like the result to be like this:
[{:name => "blake", :count => 2}, {:name => "ashley", :count => 1}]
I created a new hash called "count" then i used .each to iterate through the array and then count[:count] += 1, but it's not giving me what I'm looking for.

It can be done with built-in library calls like this:
a = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
a.group_by(&:itself) # group all of the identical elements together
.map{|k, v| k.merge(count: v.length)} # add the "count" element to the hash
Explanation
.group_by(&:itself) will take all the identical components and put them in a hash together under the same key:
>> h = a.group_by(&:itself)
=> {
{:name=>"blake"} => [{:name=>"blake"}, {:name=>"blake"}],
{:name=>"ashley"} => [{:name=>"ashley"}]
}
Notice how the first entry in the hash has an array of two identical elements, and the second entry has an array of one element. To create counts of those, we can use the .length method on the arrays in the hash's values:
>> k, v = h.first
>> v
=> [{:name=>"blake"}, {:name=>"blake"}]
>> v.length
=> 2
Then we can use .map to apply that code to every element in the hash:
>> h.map{|k, v| [k, v.length]}.to_h
=> {
{:name=>"blake"}=>2,
{:name=>"ashley"}=>1
}
Finally, .merge will take two hashes and piece them together into a single hash:
>> {:name=>"blake"}.merge({:count => 1})
=> {:name=>"blake", :count=>1}

Here the new Enumerable#tally introduced with Ruby 2.7.0 comes in hand:
h = [{:name => "blake"}, {:name => "blake"}, {:name => "ashley"}]
h.tally.map {|k, v| k.merge({count: v}) }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]

There are three main ways of doing this.
Use Enumerable#group_by
That is what #user12341234 has done in his or her answer.
Use a counting hash
If arr is your array,
arr.each_with_object(Hash.new(0)) { |g,h| h[g[:name]] += 1 }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See Hash::new for details.
Use the form of Hash#update (aka merge!) that employs a block to determine the values of keys that are present in both hashes being merged.
arr.each_with_object({}) { |g,h| h.update(g[:name]=>1) { |_,o,n| o+n } }.
map { |name, count| { :name=>name, :count=>count } }
#=> [{:name=>"blake", :count=>2}, {:name=>"ashley", :count=>1}]
See the doc Hash#update for details.
Notice that last line is the same for #2 and #3.
I cannot say that one of these approaches is preferable to the others.

Related

Looping through an array, displaying elements that match a criteria

I have this big array that I need to break down and only display specific elements within it that match a criteria.
My array looks like this.
[
{
:id => 9789,
:name => "amazing location",
:priority => 1,
:address_id => 12697,
:disabled => false
},
{
:id => 9790,
:name => "better location",
:priority => 1,
:address_id => 12698,
:disabled => false
},
{
:id => 9791,
:name => "ok location",
:priority => 1,
:address_id => 12699,
:disabled => true
}
]
What I need is to only display the elements within this array that have disabled set to true.
However when I try this, I get the error stating no implicit conversion of Symbol into Integer
array.map do |settings, value|
p hash[:disabled][:true]
end
I'm wondering if there is another way, or if there is a way to do this. If anyone could take a look, I would greatly appreciate it.
By providing two arguments to #map on an array, you're actually getting the first hash and then nil. When in reality you just want to loop for each and select those where disabled is true. You can do that instead with Array#select which will filter all elements of the array where the block returns a truthy value:
print array.select { |hash| hash[:disabled] }
=> [{:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}]
You can try this with a short each or select.
a.each { |k,_v| puts k if k[:disabled] == true }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}
This iterates over each element (hash) inside the array you have and checks if the value of the key disabled on each value is true, and puts the key, just for example, you can set it as you want to do.
Or shorter:
puts a.select { |k,_v| k[:disabled] }
=> {:id=>9791, :name=>"ok location", :priority=>1, :address_id=>12699, :disabled=>true}
Your error shows up when you are treating an array or string as a Hash.
In PHP, array keys can be either numbers or strings, whereas in Ruby associative arrays are a separate data type, called a hash.
Here’s a cheatsheet for various foreach variants, translated into idiomatic Ruby:
Looping over a numeric array (PHP) :
<?php
$items = array( 'orange', 'pear', 'banana' );
# without indexes
foreach ( $items as $item ) {
echo $item;
}
# with indexes
foreach ( $items as $i => $item ) {
echo $i, $item;
}
Looping over an array (Ruby) :
items = ['orange', 'pear', 'banana']
# without indexes
items.each do |item|
puts item
end
# with indexes
items.each_with_index do |item, i|
puts i, item
end
Looping over an associative array (PHP) :
<?php
$continents = array(
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
);
# without keys
foreach ( $continents as $continent ) {
echo $continent;
}
# with keys
foreach ( $continents as $slug => $title ) {
echo $slug, $title;
}
Looping over a hash (Ruby):
continents = {
'africa' => 'Africa',
'europe' => 'Europe',
'north-america' => 'North America'
}
# without keys
continents.each_value do |continent|
puts continent
end
# with keys
continents.each do |slug, title|
puts slug, title
end
In Ruby 1.9 hashes were improved so that they preserved their internal order. In Ruby 1.8, the order in which you inserted items into a hash would have no correlation to the order in which they were stored, and when you iterated over a hash, the results could appear totally random. Now hashes preserve the order of insertion, which is clearly useful when you are using them for keyword arguments in method definitions. (thanks steenslag for correcting me on this)

Iterate through nested hash to create an array while adding items to it

I have a nested hash and I would like to rearrange the key/val pairs. The example below shows a hash of styles that points to hash of languages, which then points to a hash of the type of language it is. I want to reformat it to look like the new_hash example. I understand to structure it by iterating through the hash through different levels and creating the hash like that, however, the part I'm concerned/confused about is creating the array that :style points to and then pushing the correct style to it.
I assumed the code snippet would work as I expect it to. My new_hash will have a key of :language which points to another hash. This hash has a key of :style that points to an array in which I will store all the styles associated with each respective language. The :javascript hash should have two styles in its array since it exists twice in the original hash, however, when running this code snippet, the array is not adding both styles. It seems that during one iteration when assigning the hash, :javascript is assigned the style of :oo but in another iteration, it gets replaced with :functional. I'm not sure of the syntax to initialize the array and add multiple items to it while iterating through the hash.
hash = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
new_hash = {
:ruby => {
:type => "Interpreted", :style => [:oo]
},
:javascript => {
:type => "Interpreted", :style => [:oo, :functional]
},
:scala => {
:type => "Compiled", :style => [:functional]
}
}
hash.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new_hash[language] = {:style => [style]}
end
end
end
You could use the forms of Hash#update (aka merge!) and Hash#merge that employ a hash to determine the values of keys that are present in both hashes being merged. See the docs for details.
hash.each_with_object({}) do |(style,language_to_type_hash),h|
language_to_type_hash.each do |language,type_hash|
h.update(language=> { type: type_hash[:type], style: [style] }) do |_,o,_|
o.merge(style: [style]) { |_,ostyle_arr,nstyle_arr| ostyle_arr + nstyle_arr }
end
end
end
#=> {:ruby =>{:type=>"Interpreted", :style=>[:oo]},
# :javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
# :scala =>{:type=>"Compiled", :style=>[:functional]}}
Hash::new allows you to specify a default value for a non existent key so in your case the default value would be {type: nil, style: []}
This functionality will allow you to loop only once and implement as follows
programming_languages = {
:oo => {
:ruby => {:type => "Interpreted"},
:javascript => {:type => "Interpreted"},
},
:functional => {
:scala => {:type => "Compiled"},
:javascript => {:type => "Interpreted"}
}
}
programming_languages.each_with_object(Hash.new {|h,k| h[k] = {type: nil, style: []}}) do |(style,languages),obj|
languages.each do |language,type_hash|
obj[language][:style] << style
obj[language][:type] = type_hash[:type]
end
end
Output:
#=> {:ruby=>{:type=>"Interpreted", :style=>[:oo]},
:javascript=>{:type=>"Interpreted", :style=>[:oo, :functional]},
:scala=>{:type=>"Compiled", :style=>[:functional]}}
Realized that this can be solved iterating the hash twice. Once to initialize the array, and the second time to then add the necessary items to it. Though not sure if this can done only iterating the hash once.
new = {}
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language] = {:type => nil , :style => []}
end
end
end
languages.each do |style, programming_language|
programming_language.each do |language, type|
type.each do |key, value|
new[language][:type] = value
new[language][:style] << style
end
end
end
new
Once we give the hashes better names, it becomes a bit easier to work it out. I've also made use of sets so we don't have to worry about duplicates.
require 'set'
# Our new hash of language info. _new to differentiate between
# the hash of languages under the hash of styles.
languages_new = {}
# For each style...
styles.each do |style, languages|
# For each language in that style...
languages.each do |language, info|
# Add a new hash for that language if there isn't one already
languages_new[language] ||= {}
# For each bit of info about that language...
info.each do |key, val|
# Add a new set for that info if there isn't one already
# The `var = hash[key] ||= new_var` pattern allows
# conditional initialization while also using either the
# new or existing set.
set = languages_new[language][key] ||= Set.new
# Add the info to it
set.add(val)
end
# Handle the special case of style.
set = languages_new[language][:style] ||= Set.new
set.add(style)
end
end
Note that rather than hard coding the initialization of hashes and sub-hashes, I've done it in each level of looping. This means I don't have to list out all the keys, and it will handle new and unexpected keys.
By using sets for the values I make no assumptions about how many values a bit of language information can have.

Ruby Hash Values is Array, need to convert to string

I have a hash of integers as keys and arrays of strings as values. I need to convert this to a new hash that inverts this relationship with each item from the array of strings in the original hash values becoming a key in the new hash and each original key becoming the associated value. For example:
original = {1 => ['a', 'b', 'c'], 2 => ['g', 'm', 'z']}
new_hash = {'a' => 1, 'b' => 1, 'c' => 1, 'g' => 2, 'm' => 2, 'z' => 2}
I'm struggling to extract the items from the original array values. It's easy enough to do
original.each { |k, v| new_hash[v] = k }
but this keeps the original array as the new key. I've tried doing something like
original.each { |k, v| new_hash[v.each { |i| i }] = k }
but this also returns the original array for some reason.
Another one, via Array#product:
original.flat_map { |k, v| v.product([k]) }.to_h
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
original.flat_map { |k, vs| vs.map { |v| {v => k} } }.reduce(&:merge)
the below snippet will give what you want, but let me think on a more readable and elegant solution.
newhash = {}
original.each do |k,v|
v.each do |v2|
newhash[v2] = k
end
end
#=> {1=>["a", "b", "c"], 2=>["g", "m", "z"]}
newhash
#=> {"a"=>1, "b"=>1, "c"=>1, "g"=>2, "m"=>2, "z"=>2}
Your approach is close. You'll have to iterate each element in the values array when assigning the new key/value pair to the newHash
newHash = {}
original.each { |k, v| v.each {|i| newHash[i] = k}}
original.map { |number, ary| Hash[ary.map { |char| [char, number] }] }.reduce(&:merge)

Conditionally extract hashes from array of hashes

From the following array of hashes:
x = [
{"creationDate"=>123456,"createdBy"=>"test_user1"},
{"creationDate"=>123459,"createdBy"=>"test_user1"},
{"creationDate"=>123458,"createdBy"=>"test_user1"},
{"creationDate"=>123454,"createdBy"=>"test_user2"},
{"creationDate"=>123452,"createdBy"=>"test_user2"},
{"creationDate"=>123451,"createdBy"=>"test_user2"}
]
I am trying to find the maximum :creationDate value where :createdBy value is "test_user1". I did this:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}
# => [123456,123459,123458,nil,nil,nil]
I want to get rid of the nil so that I can apply max to that array. How do I modify the code above?
What you want to do here is:
x.select { |record| record[:createdBy] == 'test_user1' }.map { |record| record[:creationDate] }.max
# => 123459
In general, to remove nils from an array, you can simply call Array#compact:
[1, nil, nil, 2, 'foo', nil].compact # => [1, 2, "foo"]
It' close to what you plan to do in python:
x.select { |n| n[:createdBy] == "test_user1" }.max_by { |n| n[:creationDate] }
First operation select on records created by "test_user1", while second operation get the maximum of the resulting array based on the creationDate
I'd guess something like :
x.delete_if {|x| x == nil}
This is a good candidate for a map-reduce function such as inject.
x = [{"creationDate" => 123456,"createdBy" => "test_user1"},
{"creationDate" => 123459,"createdBy" => "test_user1"},
{"creationDate" => 123458,"createdBy" => "test_user1"},
{"creationDate" => 123454,"createdBy" => "test_user2"},
{"creationDate" => 123452,"createdBy" => "test_user2"},
{"creationDate" => 123451,"createdBy" => "test_user2"}]
x.inject(nil) do |result, item|
if item["createdBy"] == "test_user1" && (result.nil? or item["creationDate"] > result)
item["creationDate"]
else
result
end
end
Here's a possible implementation of the inject. The block will iterate each item in the collection and will always maintain the highest value.
x.min_by{|h| [h[:createdBy], -h[:creationDate]]}[:creationDate]
# => 123459
[123456, 123459, 123458, nil, nil, nil].compact
# => [123456, 123459, 123458]
This works:
x.map {|a| a['creationDate'] if a['createdBy'] == 'test_user1'}.compact.max
# => 123459

Creating a Hash with keys from an array and empty arrays as the values

I have the array tags, which consists of a number of Strings that I need to use to create a new Hash (content) where each value is an empty array. I currently have Hash[*tags.map {|k| [k, nil]}.flatten], but this returns:
{
"tag1" => nil,
"tag2" => nil,
"tag3" => nil
}
when I want it to be
{
"tag1" => [],
"tag2" => [],
"tag3" => []
}
sorry this is kind of a dumb question, but I've googled around and can't find the answer. Thank you!
Using flatten, map [] instead of nil like you tried, then use flatten(1). That eliminates only the first layer of array, so you get ['tag1', [], ...] to pass to Hash[].
> tags = %w[tag1 tag2 tag3]
=> ["tag1", "tag2", "tag3"]
> tags.map {|k| [k, []]}.flatten
=> ["tag1", "tag2", "tag3"]
> tags.map {|k| [k, []]}.flatten(1)
=> ["tag1", [], "tag2", [], "tag3", []]
> Hash[*tags.map {|k| [k, []]}.flatten(1)]
=> {"tag1"=>[], "tag2"=>[], "tag3"=>[]}
You can also avoid flatten altogether if you drop the splat (*) from Hash[], since ::[] also accepts a list of pairs.
> tags.map {|k| [k, []]}
=> [["tag1", []], ["tag2", []], ["tag3", []]]
> Hash[tags.map {|k| [k, []]}]
=> {"tag1"=>[], "tag2"=>[], "tag3"=>[]}
Ruby's Array supports (Cartesian) product so you can take advantage of this, without need of extra logic built inside block(s):
> tags.product([[]]).to_h
=> {"tag1"=>[], "tag2"=>[], "tag3"=>[]}
Simple.
According to the docs Hash.new accepts a block which you can use to provide a default value each time a new key, not belonging to the Hash, is accessed.
So you can use this
tags = %w(tag1 tag2 tag3)
h = Hash.new{ |hash, key| hash[key] = [] }
tags.each{ |tag| h[tag] }
# h == {"tag1"=>[], "tag2"=>[], "tag3"=>[]}
h['tag4']
# h == {"tag1"=>[], "tag2"=>[], "tag3"=>[], "tag4"=>[]}
One more way:
tags = %w|tag1 tag2 tag3|
Hash.new { |h,k| h[k] = [] }.tap { |h| h.values_at(*tags) }
#=> {"tag1"=>[], "tag2"=>[], "tag3"=>[]}

Resources