Unpacking an array of records/fields in ruby - ruby-on-rails

Having trouble unpacking an array that is generated by rails. The array looks okay and makes sense, but unpacking it doesn't.
Here is the ruby code I have written to test it:
assets = {"0"=>{"id"=>"1", "add_asset"=>"0"},
"1"=>{"id"=>"2", "add_asset"=>"0"},
"2"=>{"id"=>"3", "add_asset"=>"1"}}
puts "assets= " + assets.to_s
puts "size = " + assets.length.to_s
assets.each_with_index do |check, i|
puts "--i = " + i.to_s
this = check[i]
puts "this = " + this.to_s
puts "id = " + this["id"].to_s
puts "add = " + this["add_asset"].to_s
end
And here is the result I am getting:
d:\Users\Michael\Documents>ruby test.rb
assets= {"0"=>{"id"=>"1", "add_asset"=>"0"}, "1"=>{"id"=>"2", "add_asset"=>"0"}, "2"=>{"id"=>"3", "add_asset"=>"1"}}
size = 3
--i = 0
this = 0
id =
add =
--i = 1
this = {"id"=>"2", "add_asset"=>"0"}
id = 2
add = 0
--i = 2
this =
test.rb:14:in `block in <main>': undefined method `[]' for nil:NilClass (NoMethodError)
from test.rb:10:in `each'
from test.rb:10:in `each_with_index'
from test.rb:10:in `<main>'
Questions:
Why is it failing to read record "0" then successfully reading record "1"?
Why is it bombing completely on record "2"?

Don't use each_with_index. You're iterating over a hash, not an array. To iterate over the keys and values of a hash, use hash.each |key,value|.
each_with_index converts your hash to an array, in the form [ [key1, value1], [key2, value2], ...]. This will give you something that looks like this:
[
["0", {"id"=>"1", "add_asset"=>"0"}],
["1", {"id"=>"2", "add_asset"=>"0"}],
["2", {"id"=>"3", "add_asset"=>"1"}]
]
So:
Why is it failing to read record "0" then successfully reading record "1"?
It isn't. It's failing all three times, it's just one failure happens to look correct.
The first iteration, i is 0 and check is equal to ["0", {"id"=>"1", "add_asset"=>"0"}], your block accesses check[0] which is "0". You then check "0"["id"] and "0"["add_asset"], which are both nil.
On the next iteration, i is 1 and check is ["1", {"id"=>"2", "add_asset"=>"0"}]. The block accesses check[1] and gets {"id"=>"2", "add_asset"=>"0"} purely by coincidence, and things seem to work.
Why is it bombing completely on record "2"?
On the third iteration, the block gets an i of 2 and check of ["2", {"id"=>"3", "add_asset"=>"1"}]. That array has no [2], so you get nil, and then your block attempts to access nil["add"] which fails.

In Ruby there is a difference between a hash and an array. Hashes can have nearly anything as a key. An array accepts only an integer. Your hashes are built with strings for keys. Decompose like this:
assets = {"0"=>{"id"=>"1", "add_asset"=>"0"},
"1"=>{"id"=>"2", "add_asset"=>"0"},
"2"=>{"id"=>"3", "add_asset"=>"1"}}
assets.each do |key, val|
puts "key = #{key}"
val.each do |inner_key, inner_val|
puts " key=#{inner_key} val=#{inner_val}"
end
end
Output:
key = 0
key=id val=1
key=add_asset val=0
key = 1
key=id val=2
key=add_asset val=0
key = 2
key=id val=3
key=add_asset val=1

Related

Ruby creating a new hash from an array of key, value

first_response = [
{"xId" => "123", "yId" => "321"},
{"xId" => "x", "yId" => "y" }
]
first_response.each do |resp|
x_id = resp['xId']
y_id = resp['yId']
puts x_id.to_s
puts y_id.to_s
end
This gives me outputs
123
321
x
y
output hash I want to create is
{123=>{321}, x=>{y}}
first service: I have an array of hash that has two different ids example:(x_id and y_id) (there would be multiple pairs like that in the response)
I want to create a hash that should contain the matching pair of x_id and y_ids that we get from the first service with x_id's as the key for all the pairs.
If you know every hash in first_response is going to contain exactly two key/value pairs, you can extract their values and then convert that result into a hash (see Enumerable#to_h):
first_response.to_h(&:values)
# {"123"=>"321", "x"=>"y"}
Looks like this approach works, but I am not completely sure if that is right
first_response = [{"xId"=>"123","yId"=> "321"}, {"xId"=>"x","yId"=> "y"}]
h = {}.tap do |element|
first_response.each do |resp|
x_id = resp['xId']
y_id = resp['yId']
element[x_id] = y_id
end
end
puts h.to_s
# {"123"=>"321", "x"=>"y"}

Accessing a hash inside an array

I am working on a coding problem where I have 3 lines of text and I have to calculate the words that appear the most in those lines. The answer is: ['it','really','will'] because the text is:
This is a really really really cool experiment really
Cute little experiment
Will it work maybe it will work do you think it will it will
Everything works in the code below except the highest_count_words_across_lines method. It's supposed to return ['it','really','will'] but instead returns 2 hashes inside an array:[{"a"=>1, "cool"=>1, "experiment"=>1, "is"=>1, "really"=>4, "this"=>1}, {"cute"=>1, "experiment"=>1, "little"=>1}, {"do"=>1, "it"=>4, "maybe"=>1, "think"=>1, "will"=>4, "work"=>2, "you"=>1}].
I've tried iterating through a hash with multiple select statements to no avail.
This is my full code so far:
class LineAnalyzer
attr_accessor :highest_wf_count, :highest_wf_words, :content, :line_number #Implement the following read-only attributes in the LineAnalyzer class.
def initialize(content, line)
#content = content #* initialize the content and line_number attributes
#line_number = line
#highest_wf_count = 0
calculate_word_frequency()
end
def calculate_word_frequency()
#highest_wf_words = Hash.new
words = #content.downcase.split
words.each { |w|
if #highest_wf_words.has_key?(w)
#highest_wf_words[w] += 1
else
#highest_wf_words[w] = 1
end
}
#highest_wf_words.sort_by { |word, count| count }
#highest_wf_words.each do |key, value|
if value > #highest_wf_count
#highest_wf_count = value
end
end
end
def highest_wf_count= (number)
#highest_wf_count = number
end
end
class Solution
attr_reader :analyzers, :highest_count_across_lines, :highest_count_words_across_lines # Implement the following read-only attributes in the Solution class.
def initialize()
#analyzers = []
highest_count_across_lines = nil
highest_count_words_across_lines = []
end
def analyze_file()
File.foreach('test.txt').with_index(1) do |content, line|
line_analyzer = LineAnalyzer.new(content, line)
#analyzers << line_analyzer
end
end
def calculate_line_with_highest_frequency()
#highest_count_across_lines = analyzers.map(&:highest_wf_count).max
#highest_count_words_across_lines = analyzers.select { |k,v| v = #highest_count_across_lines }
end
def print_highest_word_frequency_across_lines()
"The following words have the highest frequency per line: \n #{highest_count_words_across_lines} (appears in line #{line_num} \n"
end
end
This is the error message I get:
Failures:
1) Solution#calculate_line_with_highest_frequency calculates highest count words across lines to be will, it, really
Failure/Error: expect(words_found).to match_array ["will", "it", "really"]
expected collection contained: ["it", "really", "will"]
actual collection contained: [{"a"=>1, "cool"=>1, "experiment"=>1, "is"=>1, "really"=>4, "this"=>1}, {"cute"=>1, "experiment"=>1, "little"=>1}, {"do"=>1, "it"=>4, "maybe"=>1, "think"=>1, "will"=>4, "work"=>2, "you"=>1}]
the missing elements were: ["it", "really", "will"]
the extra elements were: [{"a"=>1, "cool"=>1, "experiment"=>1, "is"=>1, "really"=>4, "this"=>1}, {"cute"=>1, "experiment"=>1, "little"=>1}, {"do"=>1, "it"=>4, "maybe"=>1, "think"=>1, "will"=>4, "work"=>2, "you"=>1}]
# ./spec/solution_spec.rb:39:in `block (3 levels) in <top (required)>'
Finished in 0.26418 seconds (files took 0.38 seconds to load)
19 examples, 1 failure
Failed examples:
rspec ./spec/solution_spec.rb:31 # Solution#calculate_line_with_highest_frequency calculates highest count words across lines to be will, it, really
I've tried iterating through the hashes within an array but kept getting an error message. I am trying to find the keys where the values (counts) are equal to the highest count (4). So the final answer should be ["it","really","will"]. Any suggestions?
Step 1
Merge the array of hash into one single hash:
lets say, your array of hash is arrayOfHash
hash = arrayOfHash.inject(:merge)
Step 2
collect the keys which contains the maximum values in that single hash we created in step 1:
result = arrayOfHash.collect{|k, v| k if v == arrayOfHash.values.max}.compact

map array with condition

I have array like
strings = ["by_product[]=1", "by_product[]=2", "page=1", "per_page=10", "select[]=current", "select[]=requested", "select[]=original"]
which is array of params from request
Then there is code that generates hash from array
arrays = strings.map do |segment|
k,v = segment.split("=")
[k, v && CGI.unescape(v)]
Hash[arrays]
CUrrent output -
"by_product[]": "2",
"page":"1",
"per_page":"10",
"select[]":"original"
Expected output -
"by_product[]":"1, 2",
"page":"1",
"per_page":"10",
"select[]":"current, requested, original"
The problem is - after split method there are few by_product[] and the last one just overrides any other params, so in result instead of hash with array as value of these params im getting only last one. And i'm not sure how to fix it. Any ideas? Or at least algorithms
So try this:
hash = {}
arrays = strings.map do |segment|
k,v = segment.split("=")
hash[k]||=[]
hash[k] << v
end
output is
1.9.3-p547 :025 > hash
=> {"by_product[]"=>["1", "2"], "page"=>["1"], "per_page"=>["10"], "select[]"=>["current", "requested", "original"]}
or if you want just strings do
arrays = strings.map do |segment|
k,v = segment.split("=")
hash[k].nil? ? hash[k] = v : hash[k] << ", " + v
end
Don't reinvent the wheel, CGI and Rack can already handle query strings.
Assuming your strings array comes from a single query string:
query = "by_product[]=1&by_product[]=2&page=1&per_page=10&select[]=current&select[]=requested&select[]=original"
you can use CGI::parse: (all values as arrays)
require 'cgi'
CGI.parse(query)
#=> {"by_product[]"=>["1", "2"], "page"=>["1"], "per_page"=>["10"], "select[]"=>["current", "requested", "original"]}
or Rack::Utils.parse_query: (arrays where needed)
require 'rack'
Rack::Utils.parse_nested_query(query)
# => {"by_product[]"=>["1", "2"], "page"=>"1", "per_page"=>"10", "select[]"=>["current", "requested", "original"]}
or Rack::Utils.parse_nested_query: (values without [] suffix)
require 'rack'
Rack::Utils.parse_nested_query(query)
# => {"by_product"=>["1", "2"], "page"=>"1", "per_page"=>"10", "select"=>["current", "requested", "original"]}
And if these are parameters for a Rails controller, you can just use params.
this will also work :
strings.inject({}){ |hash, string|
key, value = string.split('=');
hash[key] = (hash[key]|| []) << value;
hash;
}
output :
{"by_product[]"=>["1", "2"], "page"=>["1"], "per_page"=>["10"], "select[]"=>["current", "requested", "original"]}
As simple as that
array.map { |record| record*3 if condition }
record*3 is the resultant operation you wanna do to the array while mapping

How do you call an index in .each_with_index

Not sure this isn't working.
>> params[:payments]
{"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"}, "1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}
So I can call a specific object with this :
>> params[:payments][:"1"]
{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}
But if I write this..
>> params[:payments].each_with_index{|item, idx| item[:"#{idx}"]}
TypeError Exception: Symbol as array index
Idealistically, I want to accomplish this :
params[:payments].each_with_index do |item, idx|
#calc.payments[idx][:date_paid] = item[:"#{idx}"][":amount_paid"]
#calc.payments[idx][:amount_paid] = (item[:"#{idx}"][":amount_paid"]).to_f
end
Update:
Based on some answers, I'ved tried this :
params[:payments].each{|k,v| #calc.payments[k.to_i] = v[":amounts_paid"]}
This turns #calc.payments into :
nil
nil
Backing up though, the others seem to work..
>> params[:payments].each{|k,v| p v[":amount_paid"]}
"80.00"
"100.00"
And this one..
>> params[:payments].each{|k,v| p #calc.payments[k.to_i]}
{:date_awarded=>"1/2/2008", :judgement_balance=>1955.96}
nil
How can I access item[idx] in a loop?
params[:payments].each do |k,v|
puts "Item %d amount=%s date=%s\n" % [k, v[":amount_paid"], v[":date_paid"]]
end
Item 0 amount=80.00 date=2/27/2008
Item 1 amount=100.00 date=5/8/2008
Update:
Ok, ok, here is a complete program .. script .. that you can actually run. Since you are trying to make sense of Ruby I think you should work with it outside of Rails for a few minutes. I mocked up #calc.payments, whatever that is. This code will run and apparently do what you want.
require 'pp'
(params = {})[:payments] = {"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"}, "1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}
pp params
class T; attr_accessor :payments; end
(#calc = T.new).payments = []
params[:payments].each do |k,v|
i = k.to_i
#calc.payments[i] ||= {}
#calc.payments[i][:date_paid] = v[":date_paid"]
#calc.payments[i][:amount_paid] = v[":date_paid"].to_f
end
pp #calc.payments
If you run it you should see:
{:payments=>
{"0"=>{":amount_paid"=>"80.00", ":date_paid"=>"2/27/2008"},
"1"=>{":amount_paid"=>"100.00", ":date_paid"=>"5/8/2008"}}}
[{:date_paid=>"2/27/2008", :amount_paid=>2.0},
{:date_paid=>"5/8/2008", :amount_paid=>5.0}]
You could just do a this to access the values. Since params[:payments] contains a hash, then for each pass through, key, will be assigned the "0", "1", etc., and value will be assigned the hash with amount_paid and date_paid.
params[:payments].each do |key, value|
amount_paid = value[":amount_paid"]
...
end

There has got to be a cleaner way to do this

I have this code here and it works but there has to be a better way.....i need two arrays that look like this
[
{
"Vector Arena - Auckland Central, New Zealand" => {
"2010-10-10" => [
"Enter Sandman",
"Unforgiven",
"And justice for all"
]
}
},
{
"Brisbane Entertainment Centre - Brisbane Qld, Austr..." => {
"2010-10-11" => [
"Enter Sandman"
]
}
}
]
one for the past and one for the upcoming...the problem i have is i am repeating myself and though it works i want to clean it up ...here is my data
..
Try this:
h = Hash.new {|h1, k1| h1[k1] = Hash.new{|h2, k2| h2[k2] = []}}
result, today = [ h, h.dup], Date.today
Request.find_all_by_artist("Metallica",
:select => "DISTINCT venue, showdate, LOWER(song) AS song"
).each do |req|
idx = req.showdate < today ? 0 : 1
result[idx][req.venue][req.showdate] << req.song.titlecase
end
Note 1
In the first line I am initializing an hash of hashes. The outer hash creates the inner hash when a non existent key is accessed. An excerpt from Ruby Hash documentation:
If this hash is subsequently accessed by a key that doesn‘t correspond to a hash
entry, the block will be called with the hash object and the key, and should
return the default value. It is the block‘s responsibility to store the value in
the hash if required.
The inner hash creates and empty array when the non existent date is accessed.
E.g: Construct an hash containing of content as values and date as keys:
Without a default block:
h = {}
list.each do |data|
h[data.date] = [] unless h[data.date]
h[data.date] << data.content
end
With a default block
h = Hash.new{|h, k| h[k] = []}
list.each do |data|
h[data.date] << data.content
end
Second line simply creates an array with two items to hold the past and future data. Since both past and the present stores the data as Hash of Hash of Array, I simply duplicate the value.
Second line can also be written as
result = [ h, h.dup]
today = Date.today

Resources