Related
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
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]
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"}]}
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
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"}},
]}