adding key values inside of an array of hashes using .each - ruby

I'm wondering what the easiest method for adding 'string' values in an array of hashes using an each statement?
lost_boys = [
{name: 'Tootles', age: '11'},
{name: 'Nibs', age: '9'},
{name: 'Slightly', age: '10'},
{name: 'Curly', age: '8'},
{name: 'The Twins', age: '9'}
]
a = []
lost_boys.each do |ages|
a = ages[:age].to_i
puts a
end

Related

Using new Ruby pattern matching to check if a hash has certain keys

I want to use the new Ruby 3 feature in this very simple case. I know it must be possible but I have not figured it out from the documentation.
Given a hash, I want to check that it has certain keys. I don't mind if it has others in addition. And I want to do this with pattern matching (or know that it is impossible.) I also don't want to use a case statement which seems overkill.
{name: "John", salary: 12000, email: "john#email.com" }
Raise an error if the hash does not have name, and email as strings and salary as a number.
Use the contruct in an if or other conditional?
And what if the hash has strings as keys (which is what I get from JSON.parse) ?
{"name" => "John", "salary" => 12000, "email" => "john#email.com" }
You're looking for the => operator:
h = {name: "John", salary: 12000, email: "john#email.com" }
h => {name: String, salary: Numeric, email: String} # => nil
With an additional pair (test: 0):
h[:test] = 0
h => {name: String, salary: Numeric, email: String} # => nil
Without the :name key:
h.delete :name
h => {name: String, salary: Numeric, email: String} # key not found: :name (NoMatchingPatternKeyError)
With the :name key but the class of its value shouldn't match:
h[:name] = 1
h => {name: String, salary: Numeric, email: String} # String === 1 does not return true (NoMatchingPatternKeyError)
A strict match:
h[:name] = "John"
h => {name: String, salary: Numeric, email: String} # => rest of {:test=>0} is not empty
The in operator returns a boolean value instead of raising an exception:
h = {name: "John", salary: 12000, email: "john#email.com" }
h in {name: String, salary: Numeric, email: String} # => true
h[:name] = 1
h in {name: String, salary: Numeric, email: String} # => false
"I also don't want to use a case statement which seems overkill." case is just the syntax for pattern matching. AFAIK it is not the same as a case when, it's a case in.
h = {name: "John", salary: 12000, email: "john#email.com", other_stuff: [1] }
case h
in {name: String, salary: Integer, email: String}
puts "matched"
else
raise "#{h} not matched"
end

"Display information for each person"

I can't get the code to display the information about the people correctly. I'm able to list the names but not the ages.
my_group = ["person_1", "person_2", "person_3"]
person_1 = {name: "erik", gender: "male", age: 26}
person_2 = {name: "erika", gender: "female", age: 26}
person_3 = {name: "erka", gender: "alpha", age: 27}
my_group.each do |name|
my_group.each do |age|
puts "Hello, #{name} is about #{age} years old"
end
puts "Hello, #{name} is about #{age} years old"
end
I don't get an error code, but it does not list the age with the names.
Actually your my_group contains only Strings. If you want to include persons variable you may include them as variables (without ""):
person_1 = {name: "erik", gender: "male", age: 26}
person_2 = {name: "erika", gender: "female", age: 26}
person_3 = {name: "erka", gender: "alpha", age: 27}
my_group = [ person_1, person_2, person_3 ]
Then you can use Array#each method to loop on each person into this array.
my_group.each do |person|
puts "Hello, #{person[:name]} is about #{person[:age]} years old"
end
Also note that each person is a Hash. If you want to access to age property of person_1, for example, you have to do this:
puts person_1[:name]

Group a array of hash according to their `:date` value by business year

I have a array of hashes that have date keys in each hash like this:
data = [
{date: Date.new(2012,1), name: "1"},
{date: Date.new(2012,8), name: "2"},
{date: Date.new(2013,2), name: "3"},
{date: Date.new(2013,6), name: "4"},
{date: Date.new(2013,9), name: "5"},
{date: Date.new(2014,3), name: "6"},
{date: Date.new(2014,4), name: "7"},
{date: Date.new(2014,8), name: "8"},
]
I want to group the hashes by business year, in other words April to next year March like this:
[
[{date: Date.new(2012,1), name: "1"}],
[{date: Date.new(2012,8), name: "2"},
{date: Date.new(2013,2), name: "3"}],
[{date: Date.new(2013,6), name: "4"},
{date: Date.new(2013,9), name: "5"},
{date: Date.new(2014,3), name: "6"}],
[{date: Date.new(2014,4), name: "7"},
{date: Date.new(2014,8), name: "8"}]
]
To accomplish I wrote like this:
result = []
4.times do |i|
result[i] = []
data.each do |datum|
result[i] << datum if datum[:date].between?(Date.new(2011+i,4), Date.new(2012+i,3))
end
end
But this code works only when I know the time frame I'm dealing with.
I feel there is a better way to write it in Ruby more concisely.
How can I write this function?
You could use Enumerable#group_by:
require 'date'
data.group_by { |h| (h[:date].month > 3) ? h[:date].year : h[:date].year - 1 }
#=> {2011=>[{:date=>#<Date: 2012-01-01 ((2455928j,0s,0n),+0s,2299161j)>, :name=>"1"}],
# 2012=>[{:date=>#<Date: 2012-08-01 ((2456141j,0s,0n),+0s,2299161j)>, :name=>"2"},
# {:date=>#<Date: 2013-02-01 ((2456325j,0s,0n),+0s,2299161j)>, :name=>"3"}],
# 2013=>[{:date=>#<Date: 2013-06-01 ((2456445j,0s,0n),+0s,2299161j)>, :name=>"4"},
# {:date=>#<Date: 2013-09-01 ((2456537j,0s,0n),+0s,2299161j)>, :name=>"5"},
# {:date=>#<Date: 2014-03-01 ((2456718j,0s,0n),+0s,2299161j)>, :name=>"6"}],
# 2014=>[{:date=>#<Date: 2014-04-01 ((2456749j,0s,0n),+0s,2299161j)>, :name=>"7"},
# {:date=>#<Date: 2014-08-01 ((2456871j,0s,0n),+0s,2299161j)>, :name=>"8"}]}

Removing hashes that have identical values for particular keys

I have an Array of Hashes with the same keys, storing people's data.
I want to remove the hashes that have the same values for the keys :name and :surname. The rest of the values can differ, so calling uniq! on array won't work.
Is there a simple solution for this?
You can pass a block to uniq or uniq!, the value returned by the block is used to compare two entries for equality:
irb> people = [{name: 'foo', surname: 'bar', age: 10},
{name: 'foo', surname: 'bar' age: 11}]
irb> people.uniq { |p| [p[:name], p[:surname]] }
=> [{:name=>"foo", :surname=>"bar", :age=>10}]
arr=[{name: 'john', surname: 'smith', phone:123456789},
{name: 'thomas', surname: 'hardy', phone: 671234992},
{name: 'john', surname: 'smith', phone: 666777888}]
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992},
# {:name=>"john", :surname=>"smith", :phone=>666777888}]
arr.uniq {|h| [h[:name], h[:surname]]}
# [{:name=>"john", :surname=>"smith", :phone=>123456789},
# {:name=>"thomas", :surname=>"hardy", :phone=>671234992}]
unique_people = {}
person_array.each do |person|
unique_people["#{person[:name]} #{person[:surname]}"] = person
end
array_of_unique_people = unique_people.values
This should do the trick.
a.delete_if do |h|
a.select{|i| i[:name] == h[:name] and i[:surname] == h[:surname] }.count > 1
end

How to retain order in XML Array to Hash conversion?

I'm trying to parse XML in Ruby using Nori, which internally uses Nokogiri.
The XML has some tags repeated and the library parses repeated tags as Arrays and non-repeated tags as normal elements (Hash)
<nodes>
<foo>
<name>a</name>
</foo>
<bar>
<name>b</name>
</bar>
<baz>
<name>c</name>
</baz>
<foo>
<name>d</name>
</foo>
<bar>
<name>e</name>
</bar>
</nodes>
is parsed as
{nodes: {
foo: [{name: "a"}, {name: "d"}],
bar: [{name: "b"}, {name: "e"}],
baz: {name: "c"}
}}
How do i retain the order of elements in the resulting hash like the output below?
{nodes: [
{foo: {name: "a"}},
{bar: {name: "b"}},
{baz: {name: "c"}},
{foo: {name: "d"}},
{bar: {name: "e"}},
]}
(This may be a library specific question. But the intention is to know if anyone has faced a similar issue and how to parse it correctly)
Nori can't do this on its own. What you can do is tune the Nori output like this:
input = {nodes: {
foo: [{name: "a"}, {name: "d"}],
bar: [{name: "b"}, {name: "e"}],
baz: {name: "c"}
}}
def unfurl(hash)
out=[]
hash.each_pair{|k,v|
case v
when Array
v.each{|item|
out << {k => item}
}
else
out << {k => v}
end
}
return out
end
output = {:nodes => unfurl(input[:nodes])}
puts output.inspect
This prints the output that the original question requested which is different than the XML order:
{nodes: [
{foo: {name: "a"}},
{foo: {name: "d"}},
{bar: {name: "b"}},
{bar: {name: "e"}},
{baz: {name: "c"}},
]}

Resources