I have this code:
#counter = 719
#period_hash = {
:sunset => 360,
:day => 720,
:dawn => 1200,
}
#period = :nothing
def init_period
periods = #period_hash.keys
#period_hash.each_with_index do |(__, number), index|
if #counter < number
#period = periods[index - 1]
break
end
end
if #period == :nothing
#period = periods[-1]
end
end
init_period
p #period
I have a #counter which will have values between 0 and 1440.
Then I have a hash with variable content. The content will always be symbol => integer
The integer value will also be a number between 0 and 1440 and all numbers are unique in
the hash. The hash will be sorted, so the lowest number will be first and highest number
will be last.
Then I have a method(init_period) which will return the key corresponding to the #counter variable.
These are the intervals for the #counter and the returned symbol:
0 .. 359 => :dawn
360 .. 719 => :sunset
720 .. 1199 => :day
1200 .. 1440 => :dawn
It all works, but Im wondering if there are other and better ways to do the same.
You can achieve converting your hash to different structure with the following code. It is not very clear, as you are using information outside from #period_hash (like global range boundaries and the fact that last value must be reused).
hash = #period_hash.
keys.
unshift(#period_hash.keys.last). # reuse last key from hash
zip( # zip keys with ranges
[0, *#period_hash.values, 1440]. # add 0 and 1440 as boundaries
each_cons(2) # convert array to consecutive pairs enum
).each_with_object({}) {|(key, (from, to)), hash|
hash[from...to] = key # build actual hash
}
On this structure you can call code that will detect your period:
hash.detect {|a,b| a.include?(719) }
You end up with really unreadable code. If your periods are not going to change often you could go for very readable code:
def init_period(counter)
case counter
when 0...360 then :dawn
when 360...720 then :sunset
when 720...1200 then :day
when 1200...1440 then :dawn
end
end
This way period boundaries are not taken from external structure, but code is obvious and clear.
Related
I'm trying to get the date to be my hash's key and then have the total of the balance be the value in my hash array to use the hash later when returning an account statement which will print date, amount and balance.
Here is the code:
class Bank
attr_accessor :total, :time
def initialize
#total = 0
#time = [:date => #total]
end
def deposit(sum)
#total += sum
end
def withdrawl(sum)
#total -= sum
end
def input_time
#time << Time.now.strftime('%d/%-m/%Y')
end
def yesterday
#time << Time.at(Time.now.to_i - 86400).strftime('%d/%-m/%Y')
end
end
How would I get the date to be the hash key? I'm currently trying to append it in but that's just adding to the array.
As I understand it, you want to add something as a key to the hash.
You can do it with merge!, something like this:
require "time"
time = DateTime.now.to_s
hash = {}
value = "value123"
hash.merge!("#{time}": "#{value123}")
p hash
#=> {:"2020-02-24T18:36:40+04:00"=>"value123"}
#merge!("KEY": "VALUE")
Here's the merge! documentation from the Ruby section on APIdock:
Adds the contents of other_hash to hsh. If no block is specified, entries with duplicate keys are overwritten with the values from other_hash, otherwise the value of each duplicate key is determined by calling the block with the key, its value in hsh and its value in other_hash.
I have created a small store application in Ruby. The concept is simple: I want to be able to add, remove, and view the 'database' in this example. The code seems to work; however, it does not behave like one would expect. I can add any product just fine, but when I try to remove them by a certain criterion that matches each item, only some are removed.
class Store
PRODUCT = Struct.new(:identifier, :parameters)
def initialize(database = [])
#database = database
end
def increment
# Increment the unique identifier if the database is not empty
# Otherwise, set the initial value for the unique identifier
#database.any? ? #database.map { |object| object[:identifier] }.max + 1 : 1
end
def add(product)
#database.push(identifier: increment, parameters: product)
end
def remove(product)
#database.each do |object|
product.each do |key, value|
#database.delete(object) if object[key] == value
#database.delete(object) if object[:parameters][key] == value
end
end
end
def view
puts #database
puts "\n"
end
end
store = Store.new
store.add(name: 'Fanta', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.add(name: 'Sprite', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.add(name: 'Coca-Cola', volume: 33, unit: 'centiliter', count: 48, price: 9.95, currency: 'SEK')
store.view
store.remove(price: 9.95)
store.view
This example will add three arbitrary items to the Store instance and list the contents found in the #database array. This array will of course hold the values:
{:identifier=>1, :parameters=>{:name=>"Fanta", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
{:identifier=>2, :parameters=>{:name=>"Sprite", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
{:identifier=>3, :parameters=>{:name=>"Coca-Cola", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
The code then proceeds to remove any product with a price of 9.95, which should match all the products in the #database array. However, when I list its contents once more, one product remains:
{:identifier=>2, :parameters=>{:name=>"Sprite", :volume=>33, :unit=>"centiliter", :count=>48, :price=>9.95, :currency=>"SEK"}}
I cannot find any logic to this. Why does my remove method not remove all the elements if the condition holds true for each comparison?
Let's focus on following looping,
#database.each do |object|
product.each do |key, value|
puts '-----------------'
#database.delete(object) if object[key] == value
#database.delete(object) if object[:parameters][key] == value
end
end
It is assumed to run 3 iteration on each loop for #database,
first iteration - it delete first with identifier = 1 for each loop counter 0
second iteration - it get modified #database as first get deleted, second become first and 3rd become 2nd and counter of each loop is at 1. So it delete new 2nd element of #database having identifier = 3.
third iteration - It will have each loop counter at 2 and new updated #database will have only 1 element having identifier = 2 (rescued in 2nd iteration) and there is no third element to proceed iteration further so nothing is iterated.
Correction needed:
def remove(product)
#database.reject! do |object|
product.map do |k,v|
object[k] == v if object.has_key?(k)
object[:parameters][k] == v if object[:parameters].has_key?(k)
end.inject(&:|)
end
end
I think that this problem comes from float comparison. There are many ways to approach this. One of them:
def remove(product)
#database.delete_if do |object|
product.any? do |key, value|
matches?(object[key], value) || matches?(object[:parameters][key], value)
end
end
end
def matches?(obj1, obj2)
if obj1.is_a?(Float) || obj2.is_a?(Float)
(obj1-obj2).abs < 0.001
else
obj1 == obj2
end
end
My Ruby assignment is to iterate through a hash and return the key associated with the lowest value, without using any of the following methods:
#keys #values #min #sort #min_by
I don't understand how to iterate through the hash and store each pair as it comes through, compare it to the last pair that came through, and return the lowest key. This is my code to show you my thought process, but it of course does not work. Any thoughts on how to do this? Thanks!
def key_for_min_value(name_hash)
index = 0
lowest_hash = {}
name_hash.collect do |key, value|
if value[index] < value[index + 1]
lowest = value
index = index + 1
key_for_min_value[value]
return lowest
end
end
end
Track min_value and key_for_min_value. Iterate through the hash, and any time the current value is lower than min_value, update both of these vars. At the end of the loop, return key_for_min_value.
I didn't include sample code because, hey, this is homework. :) Good luck!
One way to do it is transforming our hash into an array;
def key_for_min_value(name_hash)
# Convert hash to array
name_a = name_hash.to_a
# Default key value
d_value= 1000
d_key= 0
# Iterate new array
name_a.each do |i|
# If current value is lower than default, change value&key
if i[1] < d_value
d_value = i[1]
d_key = i[0]
end
end
return d_key
end
You might need to change d_value to something higher or find something more creative :)
We can use Enumerable#reduce method to compare entries and pick the smallest value. Each hash entry gets passed in as an array with 2 elements in reduce method, hence, I am using Array#first and Array#last methods to access key and values.
h = {"a" => 1, "b" => 2, "c" => 0}
p h.reduce{ |f, s| f.last > s.last ? s : f }.first
#=> "c"
I need to pick a hash entry at random, so I do
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys.sample
result = h[k]
Since h.keys creates a new array I do not like it. Is there a way to avoid creating a new array every time?
This will not generate another array. On average hash_random_value will iterate halfway through the given hash to produce a random value.
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
h = {1 => 'one', 2 => 'two', 3 => 'three'}
hash_random_value(h)
This being said, you should optimize only when you are certain you need to do that. The only way you can know is to profile your code, otherwise you are most likely doing premature optimisation. I.e. complicating your code and increasing the chance of introducing bugs--sometimes even decreasing the performance of your program. Your original solution is much easier to understand than mine, and it is immediately obvious that it is correct.
I'd like to first reiterate what most people are saying: this probably doesn't matter.
Second, I'll point out that it sure seems like you want a random value, not a random key. Maybe that's just because your example snippet of code doesn't show what you're really doing.
If you very frequently need a random value, and very infrequently update the Hash, I'd recommend caching the values any time the Hash is modified and then taking a random value from the cache. One way to do that might be like this:
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
#values = self.values
end
def sample_value
#values ||= self.values
#values.sample
end
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
rvh.sample_value
# => "one"
rvh[4] = 'four'
rvh[5] = 'five'
rvh.sample_value
# => "four"
Of course, if you really do want a random key rather than value, the exact same concept applies. Either way, this avoids recreating the Array every time you get a value; it only creates it when necessary.
If you need to make the random sample a lot, and need it to be efficient, then perhaps a Ruby Hash is not the right data structure or storage for your problem. Even a wrapper class that maintained Hash and Array attributes together might work well - if for instance for every write to the hash you needed to read 20 random samples.
Whether or not that works for you not only depends on the ratio of reading and writing, it also relates to the logical structure of your problem data (as opposed to how you've chosen to represent it in your solution).
But before you set off on re-thinking your problem, you need to have a practical need for higher performance in the affected code. The hash would need to be pretty large in order to have a noticeable cost to fetching its keys. h.keys takes about 250ms when the hash has 1 million entries on my laptop.
How about...
h = {1 => 'one', 2 => 'two', 3 => 'three'}
k = h.keys
...
result = h[k.sample]
You can do the result = h[k.sample] times as often as you like, and it won't be regenerating the k array. However, you should regenerate k any time h changes.
ADDENDUM: I'm throwing in benchmark code for several of the proposed solutions. Enjoy.
#!/usr/bin/env ruby
require 'benchmark'
NUM_ITERATIONS = 1_000_000
def hash_random_value(h)
i = rand(h.length)
h.each_with_index do |(_, v), i2|
return v if i == i2
end
end
class RandomValueHash < Hash
def []=(k, v)
super(k, v)
#values = self.values
end
def sample_value
#values ||= self.values
#values.sample
end
end
Benchmark.bmbm do |b|
h = {1 => 'one', 2 => 'two', 3 => 'three'}
b.report("original proposal") do
NUM_ITERATIONS.times {k = h.keys.sample; result = h[k]}
end
b.report("hash_random_value") do
NUM_ITERATIONS.times {result = hash_random_value(h)}
end
b.report("manual keyset") do
k = h.keys
NUM_ITERATIONS.times {result = h[k.sample]}
end
rvh = RandomValueHash[{1 => 'one', 2 => 'two', 3 => 'three'}]
b.report("RandomValueHash") do
NUM_ITERATIONS.times {result = rvh.sample_value}
end
end
Not really. Hashes don't have an index so you either convert them to an Array and pick a random index or you Enumerate your hash for a random number of times. You should benchmark which method is fastest but I doubt you can avoid creating a new object.
If you don't care about your object you could shift it's keys for a random number of times but then you'd cerate Arrays for return values.
Unless you have a gigantic hash, this is a pointless concern. Ruby is no efficiency powerhouse, and if you're that worried about this, you should be using C(++).
something like this:
h.each_with_index.reduce(nil) {|m, ((_, v), i)|
rand(i + 1) == 0 ? v : m
}
Im currently going through a book and there is a pice of code that I don't quite understand:
class RevealingReferences
attr_reader :wheels
def initialize(data)
#wheels = wheelify(data)
puts data
end
def diameters
wheels.collect do |wheel|
puts "test"
wheel.rim + (wheel.tire*2)
end
end
Wheel = Struct.new(:rim, :tire)
def wheelify(data)
data.collect{|cell|
Wheel.new(cell[0], cell[1])}
end
end
end
puts RevealingReferences.new([3,2,5,8]).diameters
and I get the following output:
3
2
5
8
test
test
test
test
3
2
1
0
1) Now the 3,2,5,8 I understand, but why does not display in array format [3,2,5,8] rather its being displayed one int at a time.
2) Also, in the wheels.collect block, the output prints "test" twice before putting in the output, should it not be "test" value "test" value
3) Also, the answer 3,2,1,0 don't make any sense, when I set #wheels should wheels not be a collection of an array of 2 elements rather then 4?
1) Now the 3,2,5,8 I understand, but why does not display in array
format [3,2,5,8] rather its being displayed one int at a time.
This is due to how puts works. When it sees an array, it prints the #to_s of each element
puts [1,2,3]
# >> 1
# >> 2
# >> 3
If you want it to look like an array, you should inspect it before printing it
puts [1,2,3].inspect
# >> [1, 2, 3]
There's also a shorthand for this, the method p
p [1,2,3]
# >> [1, 2, 3]
2) Also, in the wheels.collect block, the output prints "test" twice
before putting in the output, should it not be "test" value "test"
value
The only thing printing the values is the puts statement on the return value of diameters, so they won't print until after they have been collected. If you wanted to print it after each test, you should probably do something like
def diameters
wheels.collect do |wheel|
puts "test"
p wheel.rim + (wheel.tire*2)
end
end
Which would print:
test
3
test
2
test
1
test
0
3) Also, the answer 3,2,1,0 don't make any sense, when I set #wheels
should wheels not be a collection of an array of 2 elements rather
then 4?
Based on what you're saying here, I assume your data is not in the format you intended. You're passing in [3,2,5,8], but this implies that you meant to pass in [[3,2],[5,8]], or to map across every pair of values:
def wheelify(data)
data.each_slice(2).collect do |cell|
Wheel.new(cell[0], cell[1])
end
end
The reason it isn't doing what you think is because without doing one of these, the cell variable is actually just a number. Since numbers have the brackets method defined on them, they wind up working in this case. But the brackets method just returns 1 or 0, depending on the bit (base 2) at that position:
five = 5
five.to_s(2) # => "101"
five[2] # => 1
five[1] # => 0
five[0] # => 1
So in the case of 3, wheel.rim + (wheel.tire*2) becomes
cell = 3
cell.to_s(2) # => "11"
rim = cell[0] # => 1
tire = cell[1] # => 1
rim + tire * 2 # => 3
And in the case of 2:
cell = 2
cell.to_s(2) # => "10"
rim = cell[0] # => 0
tire = cell[1] # => 1
rim + tire * 2 # => 2
And 5:
cell = 5
cell.to_s(2) # => "101"
rim = cell[0] # => 1
tire = cell[1] # => 0
rim + tire * 2 # => 1
And 8:
cell = 8
cell.to_s(2) # => "1000"
rim = cell[0] # => 0
tire = cell[1] # => 0
rim + tire * 2 # => 0
Which is why diameters returns [3, 2, 1, 0], explaining the last four digits you see.
1) puts will output each argument on a new line, or if the argument is an array, each element of an array on a new line
2) puts "test" is running in the wheels.collect block, there are four Wheel objects created so it outputs four tests while creating the diameters array.
3) The real problem is what seems like a typo either in your book or the transfer of the code to your test environment. I think that last line was meant to read
puts RevealingReferences.new([[3,2],[5,8]]).diameters
Otherwise, the Wheel.new line
Wheel.new(cell[0], cell[1])}
is calling FixNum#[] giving you the n-th bit of the integer. This was a bit of surprise to me too - it seems like a lot could go subtly wrong when accidentally supplying an integer instead of an Array.
With the original code, cell[0] and cell[1] evaluates as 3[0] and 3[1] for the first element of data. With the correction you have the array [3,2][0] => 3, and [3,2][1] => 2 which makes much more understandable code as a "collect" example.
1- collect is a iterator method that accepts a block of code.The collect iterator returns all the elements of a collection.
2- u haven't specified the value to be displayed. do "puts wheel.rim + (wheel.tire*2)".
3- if u print the 'wheel' in the collect block of diameters method, its
"#<struct RevealingReferences::Wheel rim=1, tire=1>"
"#<struct RevealingReferences::Wheel rim=0, tire=1>"
"#<struct RevealingReferences::Wheel rim=1, tire=0>"
"#<struct RevealingReferences::Wheel rim=0, tire=0>"
When the "wheel.rim + (wheel.tire*2)" statement is executed, the result is 3,2,1,0 and each result is returned. if the statement "puts wheel" is added in the collect block for diameter and the prog executed, u wont see the values (3,2,1,0) in the output.