Testing hash contents using RSpec - ruby

I have a test like so:
it "should not indicate backwards jumps if the checker position is not a king" do
board = Board.new
game_board = board.create_test_board
board.add_checker(game_board, :red, 3, 3)
x_coord = 3
y_coord = 3
jump_locations = {}
jump_locations["upper_left"] = true
jump_locations["upper_right"] = false
jump_locations["lower_left"] = false
jump_locations["lower_right"] = true
adjusted_jump_locations = #bs.adjust_jump_locations_if_not_king(game_board, x_coord, y_coord, jump_locations)
adjusted_jump_locations["upper_left"].should == true
adjusted_jump_locations["upper_right"].should == false
adjusted_jump_locations["lower_left"].should == false
adjusted_jump_locations["lower_right"].should == false
end
which, I know, is verbose. Is there a more concise way to state my expectations? I've looked at the docs but I can't see where to compress my expectations. Thanks.

It works for hashes too:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
Source:
include matcher # relishapp.com

Just wanna add to #David's answer. You could nest and use matchers in your include hash. For example:
# Pass
expect({
"num" => 5,
"a" => {
"b" => [3, 4, 5]
}
}).to include({
"num" => a_value_between(3, 10),
"a" => {
"b" => be_an(Array)
}
})
A caveat: a nested include hash must test all keys or the test will fail, e.g.:
# Fail
expect({
"a" => {
"b" => 1,
"c" => 2
}
}).to include({
"a" => {
"b" => 1
}
})

Syntax has changed for RSpec 3, but include matcher is still the one:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
See built-in-matchers#include-matcher.

Another easy way to test if the whole content is a Hash is to checkout if the content is the Hash Object itself:
it 'is to be a Hash Object' do
workbook = {name: 'A', address: 'La'}
expect(workbook.is_a?(Hash)).to be_truthy
end
For the question above we can check as follow:
expect(adjusted_jump_locations).to match(hash_including('upper_left' => true))

Related

How does Ruby functions return a value even when there is nothing to return?

Below code converts the provided key's value in an array of hashes from JSON to hash if it is not nil. This is demonstrated in example 1.
In example 2 the provided key is nil therefore no changes are made to the data. This is the behavior I want. However I can't understand why this is happening. In example 2, the code doesn't hit line if !hash[key].nil? which means the function must return nil however it appears to be returning data_2. In ruby I understand that functions return the last evaluated statement. In example 2 what exactly is the last evaluated statement?
require 'json'
def convert(arr_of_hashes, key)
arr_of_hashes.each do |hash|
if !hash[key].nil?
begin
JSON.parse(hash[key])
rescue JSON::ParserError => e
raise "Bad"
else
hash[key] = JSON.parse(hash[key], {:symbolize_names => true})
end
end
end
end
data_1 = [ { :key_1 => "Apple", :key_2 => "{\"one\":1, \"two\":2}", :key_3 => 200 }, { :key_1 => "Orange" } ]
data_2 = [ { :key_1 => "Apple", :key_2 => nil, :key_3 => 200 }, { :key_1 => "Orange" } ]
# Example 1
p convert(data_1, :key_2)
# [{:key_1=>"Apple", :key_2=>{:one=>1, :two=>2}, :key_3=>200}, {:key_1=>"Orange"}]
# Example 2
p convert(data_2, :key_4)
# [{:key_1=>"Apple", :key_2=>nil, :key_3=>200}, {:key_1=>"Orange"}]
Consider an extremely basic example:
irb(main):003:0> a = [1, 2, 3]
=> [1, 2, 3]
irb(main):004:0> a.each { |x| p x }
1
2
3
=> [1, 2, 3]
irb(main):005:0>
The #each method
is returning the Enumerable object.
If I wrap this in a method, the method returns the last expression, which evaluates to the Enumerable object a.
irb(main):006:0> def foo(a)
irb(main):007:1> a.each { |x| puts x }
irb(main):008:1> end
=> :foo
irb(main):009:0> foo([1, 2, 3])
1
2
3
=> [1, 2, 3]
irb(main):010:0>

Convert array into hash and add a counter value to the new hash

I have the following array of hashes:
[
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
And I would like to translate this array into a hash that includes the counts for each item:
Output:
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
I have tried two approaches to translate the array into a hash.
new_cart = cart.inject(:merge)
hash = Hash[cart.collect { |item| [item, ""] } ]
Both work but then I am stumped at how to capture and pass the count value.
Expected output
{
"BREAD" => {:price => 1.50, :discount => true, :count => 2},
"MARMITE" => {:price => 1.60, :discount => false, :count => 1}
}
We are given the array:
arr = [
{"BREAD" => {:price => 1.50, :discount => true }},
{"BREAD" => {:price => 1.50, :discount => true }},
{"MARMITE" => {:price => 1.60, :discount => false}}
]
and make the assumption that each hash has a single key and if two hashes have the same (single) key, the value of that key is the same in both hashes.
The first step is create an empty hash to which will add key-value pairs:
h = {}
Now we loop through arr to build the hash h. I've added a puts statement to display intermediate values in the calculation.
arr.each do |g|
k, v = g.first
puts "k=#{k}, v=#{v}"
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
displays:
k=BREAD, v={:price=>1.5, :discount=>true}
k=BREAD, v={:price=>1.5, :discount=>true}
k=MARMITE, v={:price=>1.6, :discount=>false}
and returns:
#=> [{"BREAD" =>{:price=>1.5, :discount=>true}},
# {"BREAD" =>{:price=>1.5, :discount=>true}},
# {"MARMITE"=>{:price=>1.6, :discount=>false}}]
each always returns its receiver (here arr), which is not what we want.
h #=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
is the result we need. See Hash#key? (aka, has_key?), Hash#[], Hash#[]= and Hash#merge.
Now let's wrap this in a method.
def hashify(arr)
h = {}
arr.each do |g|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count=>1 })
end
end
h
end
hashify(arr)
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :count=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :count=>1}}
Rubyists would often use the method Enumerable#each_with_object to simplify.
def hashify(arr)
arr.each_with_object({}) do |g,h|
k, v = g.first
if h.key?(k)
h[k][:count] += 1
else
h[k] = v.merge({ :count => 1 })
end
end
end
Compare the two methods to identify their differences. See Enumerable#each_with_object.
When, as here, the keys are symbols, Ruby allows you to use the shorthand { count: 1 } for { :count=>1 }. Moreover, she permits you to write :count = 1 or count: 1 without the braces when the hash is an argument. For example,
{}.merge('cat'=>'meow', dog:'woof', :pig=>'oink')
#=> {"cat"=>"meow", :dog=>"woof", :pig=>"oink"}
It's probably more common to see the form count: 1 when keys are symbols and for the braces to be omitted when a hash is an argument.
Here's a further refinement you might see. First create
h = arr.group_by { |h| h.keys.first }
#=> {"BREAD" =>[{"BREAD"=>{:price=>1.5, :discount=>true}},
# {"BREAD"=>{:price=>1.5, :discount=>true}}],
# "MARMITE"=>[{"MARMITE"=>{:price=>1.6, :discount=>false}}]}
See Enumerable#group_by. Now convert the values (arrays) to their sizes:
counts = h.transform_values { |arr| arr.size }
#=> {"BREAD"=>2, "MARMITE"=>1}
which can be written in abbreviated form:
counts = h.transform_values(&:size)
#=> {"BREAD"=>2, "MARMITE"=>1}
See Hash#transform_values. We can now write:
uniq_arr = arr.uniq
#=> [{"BREAD"=>{:price=>1.5, :discount=>true}},
#= {"MARMITE"=>{:price=>1.6, :discount=>false}}]
uniq_arr.each_with_object({}) do |g,h|
puts "g=#{g}"
k,v = g.first
puts " k=#{k}, v=#{v}"
h[k] = v.merge(counts: counts[k])
puts " h=#{h}"
end
which displays:
g={"BREAD"=>{:price=>1.5, :discount=>true}}
k=BREAD, v={:price=>1.5, :discount=>true}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2}}
g={"MARMITE"=>{:price=>1.6, :discount=>false}}
k=MARMITE, v={:price=>1.6, :discount=>false}
h={"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
"MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
and returns:
#=> {"BREAD"=>{:price=>1.5, :discount=>true, :counts=>2},
# "MARMITE"=>{:price=>1.6, :discount=>false, :counts=>1}}
See Array#uniq.
This did the trick:
arr = [
{ bread: { price: 1.50, discount: true } },
{ bread: { price: 1.50, discount: true } },
{ marmite: { price: 1.60, discount: false } }
]
Get the count for each occurrence of hash, add as key value pair and store:
h = arr.uniq.each { |x| x[x.first.first][:count] = arr.count(x) }
Then convert hashes into arrays, flatten to a single array then construct a hash:
Hash[*h.collect(&:to_a).flatten]
#=> {:bread=>{:price=>1.50, :discount=>true, :count=>2}, :marmite=>{:price=>1.60, :discount=>false, :count=>1}}
Combined a couple of nice ideas from here:
https://raycodingdotnet.wordpress.com/2013/08/05/array-of-hashes-into-single-hash-in-ruby/
and here:
http://carol-nichols.com/2015/08/07/ruby-occurrence-couting/

Most performant way to group/summarise two hashes?

I have two hashes with some data that I need to aggregate. The first one is a mapping of which ids (id_1, id_2, id_3, id_4) belong under what category (a, b, c):
hash_1 = {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}
The second hash holds values of how many events happened per id for a given date (date_1, date_2, date_3):
hash_2 = {
'id_1' => {'date_1' => 5, 'date_2' => 6, 'date_3' => 8},
'id_2' => {'date_1' => 0, 'date_3' => 6},
'id_3' => {'date_1' => 0, 'date_2' => nil, 'date_3' => 1},
'id_4' => {'date_1' => 10, 'date_2' => 1}
}
What I want is to get the total event per category (a,b,c). For the above example, the result would look something like:
hash_3 = {'a' => (5+6+8+0+6), 'b' => (0+0+1), 'c' => (10+1)}
My problem is, that there are about 5000 categories, each pointing to typically 1 to 3 ids, and each ID having event counts for 30 dates or more. So this takes quite a bit of computation. What will be the most performant (time effective) way to do this grouping in Ruby?
update
This is what I tried so far (took like 6-8 seconds!, horribly slow):
def total_clicks_per_category
{}.tap do |res|
hash_1.each do |cat, ids|
res[cat] = total_event_per_ids(ids)
end
end
end
def total_event_per_ids(ids)
ids.reduce(0) do |memo, id|
events = hash_2.fetch(id, {})
memo + (events.values.reduce(:+) || 0)
end
end
P.S. I’m using Ruby 2.3.
I'm writing this on a phone so I cannot test right now, but it looks OK.
g = hash_2.each_with_object({}) { |(k,v),g| g[k] = v.values.compact.sum }
hash_3 = hash_1.each_with_object({}) { |(k,v),h| h[k] = g.values_at(*v).sum }
First, create an intermediate hash that holds the sum of hash_2:
hash_4 = hash_2.map{|k, v| [k, v.values.inject(:+)]}.to_h
# => {"id_1"=>19, "id_2"=>6, "id_3"=>1, "id_4"=>11}
Then do the final summation:
hash_3 = hash_1.map{|k, v| [k, v.map{|k| hash_4[k]}.inject(:+)]}.to_h
# => {"a"=>25, "b"=>1, "c"=>11}
Theory
5000*3*30 isn't that many. Ruby probably will need a second at most for this kind of job.
Hash lookup is fast by default, you won't be able to optimize much.
You could pre-calculate hash_2_sum, though :
hash_2_sum = {
'id_1' => 5+6+8,
'id_2' => 0+6,
'id_3' => 0+0+1,
'id_4' => 10+1
}
A loop on hash1 with hash_2_sum lookup, and you're done.
Code
Your example has been updated with some nil values. You need to remove them with compact, and make sure the sum is 0 when no element is found with inject(0, :+):
hash_1 = {'a' => ['id_1','id_2'], 'b' => ['id_3'], 'c' => ['id_4']}
hash_2 = {
'id_1' => { 'date_1' => 5, 'date_2' => 6, 'date_3' => 8 },
'id_2' => { 'date_1' => 0, 'date_3' => 6 },
'id_3' => { 'date_1' => 0, 'date_2' => nil, 'date_3' => 1 },
'id_4' => { 'date_1' => 10, 'date_2' => 1 }
}
hash_2_sum = hash_2.each_with_object({}) do |(key, dates), sum|
sum[key] = dates.values.compact.inject(0, :+)
end
hash_3 = hash_1.each_with_object({}) do |(key, ids), sum|
sum[key] = hash_2_sum.values_at(*ids).inject(0, :+)
end
# {"a"=>25, "b"=>1, "c"=>11}
Note
{}.tap do |res|
hash_1.each do |cat, ids|
res[cat] = total_event_per_ids(ids)
end
end
isn't very readable IMHO.
You can either use each_with_object or Array#to_h :
result = [1, 2, 3].each_with_object({}) do |i, hash|
hash[i] = i * i
end
#=> {1=>1, 2=>4, 3=>9}
result = [1, 2, 3].map { |i| [i, i * i] }.to_h
#=> {1=>1, 2=>4, 3=>9}

print out Hash key and value pairs recursively

I am trying to define a function that it can print out any hash values in a tree format. The function will do something like this:
From
{"parent1"=>
{"child1" => { "grandchild1" => 1,
"grandchild2" => 2},
"child2" => { "grandchild3" => 3,
"grandchild4" => 4}}
}
To
parent1:
child1:
grandchild1:1
grandchild2:2
child2:
grandchild3:3
grandchild4:4
And this is my code so far:
def readprop(foo)
level = ''
if foo.is_a?(Hash)
foo.each_key {|key| if foo[key].nil? == false
puts level + key + ":"
level += " "
readprop(foo[key])
end
}
else
puts level + foo
level = level[0,level.length - 2]
end
end
and it will give me a bad format like this:
parent1:
child1:
grandchild1:
1
grandchild2:
2
child2:
grandchild3:
3
grandchild4:
4
You are almost there. One way to solve it is to make level a part of the recursive function parameters. x is the hash in the question.
Simple recursive version:
def print_hash(h,spaces=4,level=0)
h.each do |key,val|
format = "#{' '*spaces*level}#{key}: "
if val.is_a? Hash
puts format
print_hash(val,spaces,level+1)
else
puts format + val.to_s
end
end
end
print_hash(x)
#parent1:
# child1:
# grandchild1: 1
# grandchild2: 2
# child2:
# grandchild3: 3
# grandchild4: 4
In this case you could also convert it to YAML (as mentioned in a comment above)
require 'YAML'
puts x.to_yaml
#---
#parent1:
# child1:
# grandchild1: 1
# grandchild2: 2
# child2:
# grandchild3: 3
# grandchild4: 4
I would use recursion, but there is another way that might be of interest to some. Below I've used a "pretty printer", awesome-print, to do part of the formatting (the indentation in particular), saving the result to a string, and then applied a couple of gsub's to the string to massage the results into the desired format.
Suppose your hash were as follows:
h = { "parent1"=>
{ "child1" => { "grandchild11" => 1,
"grandchild12" => { "great grandchild121" => 3 } },
"child2" => { "grandchild21" => { "great grandchild211" =>
{ "great great grandchild2111" => 4 } },
"grandchild22" => 2 }
}
}
We could then do the following.
require 'awesome_print'
puts str = h.awesome_inspect(indent: -5, index: false, plain: true).
gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '').
gsub(/\s*=>\s/, ':')
prints
parent1:
child1:
grandchild11:1,
grandchild12:
great grandchild121:3
child2:
grandchild21:
great grandchild211:
great great grandchild2111:4
grandchild22:2
The steps:
str = h.awesome_inspect(indent: -5, index: false, plain: true)
puts str prints
{
"parent1" => {
"child1" => {
"grandchild11" => 1,
"grandchild12" => {
"great grandchild121" => 3
}
},
"child2" => {
"grandchild21" => {
"great grandchild211" => {
"great great grandchild2111" => 4
}
},
"grandchild22" => 2
}
}
}
s1 = str.gsub(/^\s*(?:{|},?)\s*\n|[\"{}]/, '')
puts s1 prints
parent1 =>
child1 =>
grandchild11 => 1,
grandchild12 =>
great grandchild121 => 3
child2 =>
grandchild21 =>
great grandchild211 =>
great great grandchild2111 => 4
grandchild22 => 2
s2 = s1.gsub(/\s*=>\s/, ':')
puts s2 prints the result above.
Not exactly what you require but I will submit this answer as I think you may find it useful:
require 'yaml'
hash = {"parent1"=> {"child1" => { "grandchild1" => 1,"grandchild2" => 2},
"child2" => { "grandchild3" => 3,"grandchild4" => 4}}}
puts hash.to_yaml
prints:
---
parent1:
child1:
grandchild1: 1
grandchild2: 2
child2:
grandchild3: 3
grandchild4: 4
See Ruby Recursive Tree
Suppose we have
#$ mkdir -p foo/bar
#$ mkdir -p baz/boo/bee
#$ mkdir -p baz/goo
We can get
{
"baz"=>{
"boo"=>{
"bee"=>{}},
"goo"=>{}},
"foo"=>{
"bar"=>{}}}
We can traverse the tree as the following. So, here's a way to make a Hash based on directory tree on disk:
Dir.glob('**/*'). # get all files below current dir
select{|f|
File.directory?(f) # only directories we need
}.map{|path|
path.split '/' # split to parts
}.inject({}){|acc, path| # start with empty hash
path.inject(acc) do |acc2,dir| # for each path part, create a child of current node
acc2[dir] ||= {} # and pass it as new current node
end
acc
}
Thanks to Mladen Jablanović in the other answer for this concept.

Access to merged cells using Ruby-Roo

According to example below: Value is stored only in A1, other cells return nil.
How is possible to get the A1'a value from the others merged cells, or simply check range of the A1 cell?
here is my take, if all merged fields are same as prev - then non-merged fields should become array
xlsx = Roo::Excelx.new(__dir__ + "/output.xlsx", { expand_merged_ranges: true })
parsed = xlsx.sheet(0).parse(headers: true).drop(1)
parsed_merged = []
.tap do |parsed_merged|
parsed.each do |x|
if parsed_merged.empty?
parsed_merged << {
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => [x["field_merged1"]],
"field_merged2" => [x["field_merged2"]],
"field_merged3" => [x["field_merged3"]],
"field_merged4" => [x["field_merged4"]],
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
else
field_merged1_is_same_as_prev = x["field_non_merged1"] == parsed_merged.last["field_non_merged1"]
field_merged2_is_same_as_prev = x["field_non_merged2"] == parsed_merged.last["field_non_merged2"]
field_merged3_is_same_as_prev = x["field_non_merged3"] == parsed_merged.last["field_non_merged3"]
merged_rows_are_all_same_as_prev = field_non_merged1_is_same_as_prev && field_merged2_is_same_as_prev && field_merged3_is_same_as_prev
if merged_rows_are_all_same_as_prev
parsed_merged.last["field_merged1"].push x["field_merged1"]
parsed_merged.last["field_merged2"].push x["field_merged2"]
parsed_merged.last["field_merged3"].push x["field_merged3"]
parsed_merged.last["field_merged4"].push x["field_merged4"]
else
parsed_merged << {
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => [x["field_merged1"]],
"field_merged2" => [x["field_merged2"]],
"field_merged3" => [x["field_merged3"]],
"field_merged4" => [x["field_merged4"]],
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
end
end
end
end
.map do |x|
{
"field_non_merged1" => x["field_non_merged1"],
"field_merged1" => x["field_merged1"].compact.uniq,
"field_merged2" => x["field_merged2"].compact.uniq,
"field_merged3" => x["field_merged3"].compact.uniq,
"field_merged4" => x["field_merged4"].compact.uniq,
"field_non_merged2" => x["field_non_merged2"],
"field_non_merged3" => x["field_non_merged3"],
}
end
This is not possible without first assigning the value to all the cells of the range, even in Excel VBA this is the case.
See this sample
require 'axlsx'
p = Axlsx::Package.new
wb = p.workbook
wb.add_worksheet(:name => "Basic Worksheet") do |sheet|
sheet.add_row ["Val", nil]
sheet.add_row [nil, nil]
merged = sheet.merge_cells('A1:B2')
p sheet.rows[0].cells[0].value # "Val"
p sheet.rows[0].cells[1].value # nil
sheet[*merged].each{|cell|cell.value = sheet[*merged].first.value}
p sheet.rows[0].cells[0].value # "Val"
p sheet.rows[0].cells[1].value # "Val"
end
p.serialize('./simple.xlsx')
Please add a sample yourself next time so that we see which gem you used, which code, error etc.

Resources