creating dynamic hash with key and value in ruby on rails - ruby-on-rails

I am trying to create Hash with dynamic key and respective values. For example like this
hash = {1 => 23.67, 1 => 78.44, 3 => 66.33, 12 => 44.2}
Something like this in which 1,2,12 are array index. I hope it is understandable. I am trying with the syntax from ROR tutorials.
Like this
test = Hash.new
for i in 0..23
if (s.duration.start.hour == array[i].hour)
s.sgs.each do |s1|
case s1.type.to_s
when 'M'
test ={i => s1.power} # here I am trying to create hash like give example in which i is for loop value
when 'L'
puts "to be done done"
else
puts "Not Found"
end
end
end
end
end
Updated code
test = Hash.new
for i in 0..23
if (s.duration.start.hour == array[i].hour)
s.sgs.each do |s1|
case s.type.to_s
when 'M'
puts s1.power;
test[i] = s1._power
when 'L'
puts "to be done"
else
puts "Not Found"
end
end
end
end
Results
on traversing
for t in 0..array.size
puts test[t]
end
Results :
t = 68.6 # which is last value
and expected
t = 33.4
t = 45.6 etc
Sample logs
after assign {23=>#<BigDecimal:7f3a1e9a6870,'0.3E2',9(18)>}
before assign {23=>#<BigDecimal:7f3a1e9a6870,'0.2E2',9(18)>}
after assign {23=>#<BigDecimal:7f3a1e9ce550,'-0.57E2',9(18)>}
before assign {23=>#<BigDecimal:7f3a1e9ce550,'-0.57E2',9(18)>}
if any other optimised solution is there would be good thanks

You are re-assigning test with a new hash on each iteration. You should add to it, so instead of
test ={i => s1.power}
you should do:
test[i] = s1.power
This sets the value of key i to s1.power
If you want to keep an array of all the values for a given key, I would suggest the following (more ruby-ish) solution:
hour_idx = array.find_index { |item| s.duration.start.hour == item.hour }
values = case s.type.to_s
when 'M'
s.sgs.map(&:_power)
when 'L'
puts "to be done"
else
puts "Not Found"
end
test = { hour_idx => values }
What I'm doing here is:
Find the hour_idx which is relevant to the current s (I assume there is only one such item)
Create an array of all the relevant values according to s.type (if it is 'M' an array of all the _power of s.sgs, for 'L' whatever map you need, and nil otherwise)
Create the target hash using the values set in #1 and #2...

Related

Checking if input == Integer/float? using irb

How to check if users didn't put an integer in if statement in a loop. I want the if statement to check if the user didn't put an integer, the system gonna say "please put an integer" Right now, everything works fine, but when user input a decimal number, the program will convert it to an integer. I want the program to say "you need to put an integer" when user put a decimal number.
I tried if soap.match(/^[\d]+$/).nil? and other methods, but didn't work. I think I need to change some code in 'if (soap_type = SOAPS[soap.to_i])', but I don't know how to change it.
SOAPS = {
1 => 'face soap',
2 => 'bar soap',
3 => 'shave soap',
4 => 'shampoo soap'
}.freeze
loop do
puts "What type of soaps do you want? (#{SOAPS.map { |k, v| "#{k} - #{v}" }.join(', ')}) Please put a number from 1 - 4 "
soap = gets
if (soap_type = SOAPS[soap.to_i])
puts "Great! You want #{soap_type}."
break
elsif !soap.match (/\A[-+]?[0-9]*\.?[0-9]+\Z/)
puts "You didn't enter an integer, please put an integer from 1-4"
else
puts "#{soap.inspect} is not a valid integer, please try again."
end
end
I hope when the user input a decimal number, the program gonna say You didn't enter an integer, please put an integer.
There's a much more Ruby way to express this, the idiomatic form if you will:
loop do
puts "What type of soaps do you want? (1 = face soap, 2 = bar soap, 3 = shave soap, 4 = shampoo soap) Please put a number from 1 - 4 "
case (soap = gets)
when '1'
puts "Great! You want face soap."
when '2'
puts "Great! You want bar soap"
when '3'
puts "Great! You want shave soap."
when '4'
puts "Great! You want shampoo soap."
else
puts "#{soap.inspect} is not a valid entry, please try again."
end
end
Where the case statement can do a ton of work for you. Here the string values are being tested instead of bothering to convert since that doesn't really matter.
Notice there's still a lot of duplication though. This is where a look-up table can be the key to simplifying things:
SOAPS = {
1 => 'face soap',
2 => 'bar soap',
3 => 'shave soap',
4 => 'shampoo soap'
}.freeze
loop do
puts "What type of soaps do you want? (#{SOAPS.map { |k, v| "#{k} - #{v}" }.join(', ')}) Please put a number from 1 - 4 "
soap = gets
if (soap_type = SOAPS[soap.to_i])
puts "Great! You want #{soap_type}."
else
puts "#{soap.inspect} is not a valid entry, please try again."
end
end
Where now you can easily add and remove entires and everything updates accordingly. This is a data-driven approach and it's what Ruby excels at because once you have something expressed in terms of data, you can build on that with a series of transformations to get the desired result.

Counter for parallel processes in Ruby

I have this code that uses the parallel gem to split work among different processes.
Parallel.map(list, :in_processes=>4) do |item|
if item.name == "A"
puts "A"
else
puts "B"
end
end
What would be the best way to have a counter shared between the processes in order to get the exact number of times I got A and B?
Receive the Parallel's returned value (array of returned values from each iteration), and use Array#count method.
list = ["A","B","A","B","A","B","A","B","A","B"]
result = Parallel.map(list, :in_processes=>4) do |item|
if item == "A"
puts "Got A"
return "A"
else
puts "Got B"
return "B"
end
end
result # Array of "A" and "B" like ["A","B","A","A","B","B","A","A","B","B"]
# Now, you can do whatever you want with the result array.
count_a = result.count("A") # 5
count_b = result.count("B") # 5
(Ruby doesn't need explicit return keyword, but I put it to avoid any misunderstandings.)

Link method argument to a variable

I have a program where a user is able to receive popular "vacation spots". Al they have to do is enter the continent (Which will bring them to that dictionary) and then enter a country/state (which is a key in a hash) and then it will find the corresponding value.
I have a required file (dict.rb) which is basically a hash module using arrays.
But the issue I have is fairly small. I assigned the user input to two variables, continent_select and country_select
Here's the code:
require './dict.rb'
#create a new dictionary called northamerica
northamerica = Dict.new
Dict.set(northamerica, "new york", "New York City")
Dict.set(northamerica, "new jersey", "Belmar")
puts "Welcome to The Vacation Hub"
puts "What continent are you interested in?"
print '> '
continent_select = $stdin.gets.chomp.downcase
continent_select.gsub!(/\A"|"\Z/, '')
puts "Which state would you like to go to in #{continent_select}"
print '> '
country_select = $stdin.gets.chomp.downcase
#puts "You should go to #{Dict.get(northamerica, "#{country_select}")}"
#=> You should go to Belmar
puts "You should go to #{Dict.get(continent_select, "#{country_select}")}"
#=> error
Ignore the get and set methods, they're in the included dict.rb
Anyway look carefully at the last few lines. The Dict.get method has two arguments. The first finds which dictionary to use. If I just put northamerica as an argument it works. But if I put continent_select instead (assuming the user enters 'northamerica') it doesn't work. I think the program is looking for a Dictionary named continent_select, rather than looking for the variable continent_select.
UPDATE
Here's the whole dict.rb for those who asked.
module Dict
#creates a new dictionary for the user
def Dict.new(num_buckets=256)
#initializes a Dict with given num of buckets
#creates aDict variable which is an empty array
#that will hold our values later
aDict = []
#loop through 0 to the number of buckets
(0...num_buckets).each do |i|
#keeps adding arrays to aDict using push method
aDict.push([])
end
return aDict
#returns [[],[],[]] => array of empty arrays reading to go.
end
def Dict.hash_key(aDict, key)
# Given a key this will create a number and then convert
# it to an index for the aDict's buckets.
return key.hash % aDict.length
#key.hash makes the key a number
# % aDict.length makes the number between 1 and 256
end
def Dict.get_bucket(aDict, key)
#given a key, find where the bucket would go
#sets the key to a number and it's put in bucket_id variable
bucket_id = Dict.hash_key(aDict, key)
#finds the key number in the dict, and returns the key
return aDict[bucket_id]
end
def Dict.get_slot(aDict, key, default=nil)
#returns the index, key, and value of a slot found in a bucket
#assigns the key name to the bucket variable
bucket = Dict.get_bucket(aDict, key)
bucket.each_with_index do |kv, i|
k, v = kv
if key == k
return i, k, v
#returns index key was found in, key, and value
end
end
return -1, key, default
end
def Dict.get(aDict, key, default=nil)
#Gets the value in a bucket for the given key, or the default
i, k, v = Dict.get_slot(aDict, key, default=default)
return v
end
def Dict.set(aDict, key, value)
#sets the key to the value, replacing any existing value
bucket = Dict.get_bucket(aDict, key)
i, k, v = Dict.get_slot(aDict, key)
if i >= 0
bucket[i] = [key, value]
else
bucket.push([key, value])
end
end
def Dict.delete(aDict, key)
#deletes. the given key from the Dict
bucket = Dict.get_bucket(aDict, key)
(0...bucket.length).each do |i|
k, v = bucket[i]
if key == k
bucket.delete_at(i)
break
end
end
end
def Dict.list(aDict)
#prints out what's in the dict
aDict.each do |bucket|
if bucket
bucket.each {|k, v| puts k, v}
end
end
end
end
Now there's some weird stuff going on.
In the first case, which seems to be okay, you pass the correct arguments:
Dict.get(northamerica, "#{country_select}")
That is: Dict instance as the first argument, and a String as the second. But then in the second case:
Dict.get(continent_select, "#{country_select}")
You pass a String instance instead of an obviously expected Dict, and this results in an error.
As far as I understand your intention, you want user input to become a variable name to be used as the first argument, but there is no way way it is magically happening, and you end you up passing just a string.
What you need to do is explicitly map a user input to a corresponding Dict object, and then use it. It can look like this:
# fetch a Dict object that corresponds to "northamerica" string from a hash
# NOTE: it will raise an exception if a user enters something that's not present
# in a hash, i.e. something other than "northamerica"
selected_continent_dict = { "northamerica" => northamerica }.fetch(continent_select)
puts "You should go to #{Dict.get(selected_continent_dict, country_select)}"
If you're prohibited to use Ruby hashes, you can easily get away with a case statement:
selected_continent_dict = case continent_select
when "northamerica"
northamerica
else
raise "Invalid continent"
end
puts "You should go to #{Dict.get(selected_continent_dict, country_select)}"
Hope this helps!
P.S. Two more advice, if you don't mind:
There's no real need for string interpolation in the second argument, and something like Dict.get(northamerica, country_select) could be a cleaner way.
Better variable naming could save you from headaches. I.e. if you renamed a (quite misleading) country_select to a user_state_selection_string it would remind you that it is a string, and of what it holds. The example is arbitrary though. There's a wonderful book called "Code Complete" by Steve McConnell which covers this and other issues much better than I do.

determining if at least one pair of fields are filled out

So in my rails app I have a column of text boxes in pairs. There are 4 pairs of textboxes.
Just one of these pairs of text box needs to be filled out (with both textboxes in that pair filled out) for it to be valid.
So I'm trying to loop through them all and add them to a multi dimensional array and to check that at least one row(?) in the array has both values.
def no__values?
all_values = Array.new
txt_a_values = Array.new
txt_b_values = Array.new
self.item.line_items.each do |item|
txt_a_values << item.single.nil? # this is a value from the text box a
txt_b_values << item.aggregate.nil? # this is a value from the text box b
end
all_values << txt_a_values #create multi dimensional array
all_values << txt_b_values
all_values.each do |v|
??? # there needs to be one pair in the array which has both values
end
end
So it should create an array like this
[true][true] # both textboxes are nil
[false][false] # both textboxes have values
[true][true] # both textboxes are nil
[true][true] # both textboxes are nil
the above scenario is valid since there is one pair which BOTH have values
I really don't like the way I'm progressing with this, so I',m looking for some help.
Couldn't you do something like this:
def no__values?
self.item.line_items.each do |item|
return false if !item.single.nil? && !item.aggregate.nil?
end
true
end
That will return false when both values of a pair isn't null, and returns true if each pair had at least one null value.
Edit:
Based on the method name I didn't create the array and instead returns false/true directly.
I think you are looking for the following snippets to solve your problems. I have done it with little simplification. Please let me know if you are looking for this.
def no__values?
all_values = []
self.item.line_items.each do |item|
all_values << [item.single.nil?, item.aggregate.nil?]
end
all_values.each do |v|
puts v # [true, false] or [true, true], [false, false]....
end
end

String won't show up

I get a bool value as a parameter and assign it to a variable
#package = params[:package]
Now, I have a method which should return a string based on the value of package. The method looks like this:
def get_description(package)
if package == 1
"foo"
elsif package == 2
"bar"
end
end
In another method, I try to get the #description by:
#description = get_description(#package)
The problem is, the string is not displayed. The #description variable stays empty. Maybe I'm too stupid to see the bug?
If you want you can use your code changing the value of package to integer:
def get_description(package)
package = package.to_i
if package == 1
"foo"
elsif package == 2
"bar"
end
end
Or using a case statement:
def get_description(package)
case package.to_i
when 1
"foo"
when 2
"bar"
end
end
All parameters arrive in your controller as strings, regardless of what type you think they should be. This is even true if the parameter was originally written to the browser by a View using an Integer. This is just because of the way browsers send the data back to the server.
You'll need to convert the parameter to an integer, or compare it with the strings "1" and "2".

Resources