Just wondering, is there a way to use macros in Ruby that does an in-text substitution the way C would work?
For example:
define ARGS 1,2
sum(ARGS) # returns 3
EDIT:
More specifically my problem looks more like:
#button1 = FXButton.new(self, "Button 1",:opts => BUTTONPROPERTIES,:width => width, :height => height)
#button2 = FXButton.new(self, "Button 2",:opts => BUTTONPROPERTIES,:width => width, :height => height)
#button3 = FXButton.new(self, "Button 3",:opts => BUTTONPROPERTIES,:width => width, :height => height)
And ideally I'd want the code to look like:
#button1 = FXButton.new(self, "Button 1", ALLBUTTONPROPERTIES)
#button2 = FXButton.new(self, "Button 2", ALLBUTTONPROPERTIES)
#button3 = FXButton.new(self, "Button 3", ALLBUTTONPROPERTIES)
Notice how I have "width" and "height" variables that won't properly be passed to the initialization of the FXButton class if I just set them to some predetermined value. Is there some kind of code substitution that would take care of this issue?
You don't need a macro. Just define a variable or a constant.
A = [1, 2]
A.inject(:+) # => 3
After the edit to your question
You can do like this:
ALLBUTTONPROPERTIES = ->{{opts: => BUTTONPROPERTIES, width: width, height: height}}
and within a context where the constants and variables BUTTONPROPERTIES, width, height are assigned some value, do this:
#button1 = FXButton.new(self, "Button 1", ALLBUTTONPROPERTIES.call)
#button2 = FXButton.new(self, "Button 2", ALLBUTTONPROPERTIES.call)
#button3 = FXButton.new(self, "Button 3", ALLBUTTONPROPERTIES.call)
There's probably another way around what you are trying to do. Preprocessors macros doesn't make sense because Ruby is not a compiled language, it is an interpreted language.
Particularly for your example there's a very clean way to do that:
args = [1, 2]
sum(*args) # equivalent to sum( 1, 2 )
There is no preprocess in ruby, a macros does not make sense. Simply use string constant or whatever other type of constant you need.
Ruby way of solving your problem would probably be a bit different from your approach:
#buttons = ["Button 1", "Button 2", "Button 3"].map do |name|
FXButton.new(self, name,
:opts => BUTTONPROPERTIES,
:width => width,
:height => height)
end
In this example you don't have #button1, #button2, #button3 variables. Instead #buttons is array containing all three.
Related
I'm trying to make a calorie counter for the below hash menu. in this example I've passed 3 arguments - what would the function need to look like it the number of parameters/arguments is unknown?
#menu = {
"hamburger" => 250,
"Cheese burger" => 350,
"cola" => 35,
"salad" => 120,
"dessert" => 350
}
def order(a, b, c)
return #menu[a] + #menu[b] + #menu[c]
end
puts order("hamburger", "Cheese burger", "cola")
tried
def order(**a)
total = 0
total += #menu[**a]
end
i know (*a) works for arrays.
I'd like to be able to get results for
puts order("hamburger")
and equally for
puts order("Cheese burger", "salad"), for example
In Ruby, it is often possible to write the code exactly the same way you would describe the solution in English. In this case, you need to get the values at specific keys of the hash and then compute the sum of the values.
You can use the Hash#values_at method to get the values at the specific keys and you can use the Array#sum method to compute the sum of the values:
def order(*items)
#menu.values_at(*items).sum
end
Note that it is strange to use an instance variable of the top-level main object. It would make much more sense to use a constant:
MENU = {
'hamburger' => 250,
'Cheese burger' => 350,
'cola' => 35,
'salad' => 120,
'dessert' => 350,
}
def order(*items)
MENU.values_at(*items).sum
end
It would also make sense to freeze the hash:
MENU = {
'hamburger' => 250,
'Cheese burger' => 350,
'cola' => 35,
'salad' => 120,
'dessert' => 350,
}.freeze
And last but not least, I find the name of the order method somewhat misleading. It is also ambiguous: is order meant to be a noun and this is meant to be a getter method that retrieves an order? Or is it meant to be a verb and it is meant to be a command method which tells the object to execute an order?
Either way, it does not seem that the method is doing either of those two things, rather it seems to compute a total. So, the name should probably reflect that.
I would do:
MENU = {
"hamburger" => 250,
"Cheese burger" => 350,
"cola" => 35,
"salad" => 120,
"dessert" => 350
}
def order(*args)
MENU.values_at(*args).sum
end
order("hamburger", "Cheese burger", "cola")
#=> 635
Read about the Ruby Splat Operator, Hash#values_at and Array#sum.
When you really want to use each (what I would not recommend), like mentioned in the comment, then you can implement it like this:
def order(*args)
total = 0
args.each { |name| total += MENU[name] }
total
end
or
def order(*args)
total = 0
MENU.values_at(*args).each { |value| total += value }
total
end
I've been fighting with Tkinter in ruby to try and get a proper scrollable frame working.
I've had the most success with this tutorial: https://www.youtube.com/watch?v=VmlgrrXAqb4
I have translated what he does in the video into ruby and it "works", but only kinda. When I run the application I get a window similar to his with all of the buttons, but the scrollbar handle doesn't appear in it's frame and I can scroll past the content of the frame in both directions.
I'm trying to build an application that has a heavy use of scrolling, but I can't get past this. I am posting as a last resort. I've tried most of the examples I can find online, but I keep getting unexpected behavior. It is worth noting that I am on MacOS in case someone tries the code on Windows or Linux and it works for some reason.
require 'tk'
require 'tkextlib/tkimg'
win = TkRoot.new
win.geometry "500x500"
win.resizable false,false
wrapper1 = Tk::Tile::Frame.new win
wrapper2 = Tk::Tile::Frame.new win
mycanvas = TkCanvas.new wrapper1
mycanvas.pack :side => "left", :fill => "both", :expand => "yes"
yscrollbar = TkScrollbar.new :parent => wrapper1, :command => Proc.new {|*args| mycanvas.yview *args}
yscrollbar.pack :side => "left", :fill => "y"
mycanvas.configure(:yscrollcommand => Proc.new {|*args| yscrollbar.set *args })
mycanvas.bind "<Configure>", Proc.new {mycanvas.scrollregion = mycanvas.bbox("all")}
myframe = Tk::Tile::Frame.new mycanvas
TkcWindow.new(mycanvas, 0, 0, :anchor => "nw", :window => myframe)
wrapper1.pack :fill => "both", :expand => "yes", :padx => 10, :pady => 10
wrapper2.pack :fill => "both", :expand => "yes", :padx => 10, :pady => 10
for i in 0..50
button = TkButton.new(myframe)
button.text = "Button " + i.to_s
button.pack
end
Tk.mainloop
I figured out what was wrong.
#The line of code below where the Configure event is bound needs to be:
mycanvas.bind "Configure" #...
#instead of:
mycanvas.bind "<Configure>" #...
I have an array and a hash:
a = [
{ :color => "blue", :name => "wind" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "yellow", :name => "wind" },
{ :color => "green", :name => nil },
{ :color => "black", :name => "snow" }
]
b = { blue: 'blue', white: 'white', red: 'red', green: 'green', black: 'black' }
I need to find out unique names based on the input hash to get this:
['wind', 'fire', 'snow']
I've tried:
names = a.map { |i| [i[:color], i[:name]] }
.delete_if { |key, value| value.nil? }
resultant_names = []
b.values.each do |c|
if names[c]
resultant_names << names[c]
end
end
resultant_names.uniq
I need a better approach for this. This one has too many loops.
While your result does not make sense to me (e.g. it is missing snow) this will work
a.map(&:values).reverse.to_h.values_at(*b.values).compact.uniq
#=> ["wind","fire"]
To break it down:
a.map(&:values).reverse.to_h
#=> {"white"=>"wind", "green"=>nil, "yellow"=>"wind", "red"=>"fire", "blue"=>"wind"}
You'll notice snow is missing because when we reverse the list ["white","wind"] will overwrite ["white","snow"] when converted to a Hash
Then we just collect the values for the given colors from
b.values
#=> ["blue", "white", "red", "green"]
a.map(&:values).reverse.to_h.values_at(*b.values)
#=> ["wind", "wind", "fire", nil]
Then Array#compact will remove the nil elements and Array#uniq will make the list unique.
If snow was intended you could skip the reversal
a.map(&:values).to_h.values_at(*b.values).compact.uniq
#=> ["wind", "snow", "fire"]
Either way this is a strange data structure and these answers are only to help with the problem provided as the duplicate colors can cause differing results based on the order in a.
I believe you want 'snow' to be in your output array, as there is no other logical explanation. Your code would work if you were to add .to_h on the end of line 2, but as you note, it is not very clean or efficient. Also, by converting to a Hash, as a result of duplicate keys, you would potentially lose data.
Here's a tighter construct that avoids the data loss problem:
def unique_elements(a, b)
color_set = b.values.to_set
a.map { |pair| pair[:name] if color_set.include?(pair[:color]) }.compact.uniq
end
First we take the values of b and convert them to a set, so that we can efficiently determine if a given element is a member of the set.
Next we map over a choosing the names of those members of a for which the [:color] is included in our color set.
Finally we eliminate nils (using compact) and choose unique values.
>> unique_elements(a, b)
#> ["wind", "fire", "snow"]
I would begin by converting a to a more useful data structure.
h = a.each_with_object({}) { |g,h| h[g[:color]] = g[:name] }
#=> {"blue"=>"wind", "red"=>"fire", "white"=>"wind", "yellow"=>"wind",
# "green"=>nil, "black"=>"snow"}
We may then simply write
h.values_at(*b.values).compact.uniq
# => ["wind", "fire", "snow"]
This approach has several desireable characteristics:
the creation of h makes the method easier to read
debugging and testing is made easier by creating h as a separate step
h need only be created once even if several values of b are to be evaluated (in which case we may wish to make h an instance variable).
h could be chained to the second statement but I've chosen not to do so for the reasons given above (especially the last one).
How do you force a float to display with all significant places / full precision without scientific notation in Ruby?
Presently I convert a BigDecimal to Float, BigDecimal(0.000000001453).to_f, however this yields a resultant float of 1.453e-09. If I do something like "%14.12f" % BigDecimal("0.000000001453").to_f I get a string. In this case, however, a string as output is unacceptable as I need it as an actual numeric float without scientific notation.
--- Edit---
Alright, let me give some context here, which will probably require a change of my original question.
I'm attempting to create a graph with Highstock & lazy_high_chart. Earlier today I was able to draw graphs just fine when the floats were emitting to the resultant js as full precision floats vs showing up in scientific notation. Hence I felt that the problem resides in this issue.
But after the few inputs I'm getting here, perhaps I need some further review of the source and my assumption is misplaced. I'll let you decide:
#h = LazyHighCharts::HighChart.new('graph') do |f|
hours_of_readings = 1
reading_intervals = 1 #hour
readings_per_hour = 60
readings = ModelName.order("date DESC").select('data_readings.data2, data_readings.data1, data_readings.date').limit(hours_of_readings * readings_per_hour).all
data1_and_date_series = Array.new
data2_and_date_series = Array.new
dates = Array.new
# I have been thinking that the problem lies here in the "row.data1.to_f" and
# "row.data2.to_f" and thus this is the root of my initial question in terms
# of it emitting scientific notation to the js output in the format of:
# [[1.0e-09], [1.04e-09],[9.4e-09], ... [3.68e-09]]
data1_and_date_series = readings.map{|row| [(row.date.to_i * 1000), (row.data1.to_f if BigDecimal(row.data1) != BigDecimal("-1000.0"))] }
data2_and_date_series = readings.map{|row| [(row.date.to_i * 1000), (row.data2.to_f if BigDecimal(row.data2) != BigDecimal("-1000.0"))] }
f.series(
:name => 'Data1',
:data => data1_and_date_series,
:pointStart => Time.now.to_i * 1000,
:pointEnd => hours_of_readings.hours.ago.to_i * 1000,
:pointInterval => reading_intervals.hour * 1000,
:color => 'blue'
)
f.series(
:name => 'Data2)',
:data => data2_and_date_series,
:pointStart => Time.now.to_i * 1000,
:pointEnd => hours_of_readings.hours.ago.to_i * 1000,
:pointInterval => reading_intervals.hour.to_i * 1000,
:color => 'red'
)
f.chart({:defaultSeriesType=>"spline" })
f.yAxis [
{:title => { :text => "Label 1", :margin => 10} },
{:title => { :text => "Label 2 (groups)"}, :opposite => true},
{:max => 0},
{:min => -0.000000001}
]
f.options[:xAxis] = {
:title => { :text => "Time"},
:type => "datetime"
}
f.title(:text => "Title")
f.legend(:align => 'right', :verticalAlign => 'top', :y => 75, :x => -50, :layout => 'vertical') # override the default values
end
The string representation and the actual value of a float are two different things.
What you see on screen/print-out is always a string representation, be it in scientific notation or "normal" notation. A float is converted to its string representation by to_s, puts, "%.10f" % and others.
The float value itself is independent of that. So your last sentence does not make much sense. The output is always a string.
To enforce a certain float format in Rails' to_json you can overwrite Float#encode_json, e.g.
class ::Float
def encode_json(opts = nil)
"%.10f" % self
end
end
Put this before your code above. Note that -- depending on your actual values -- you might need more sophisticated logic to produce reasonable strings.
Will this work for you -
>> 0.000000001453
=> 1.453e-09 # what you are getting right now
>> puts "%1.12f" % 0.000000001453
0.000000001453 # what you need
=> nil
I feel like this could be improved (a common feeling in ruby). I'm trying to uniq an array of hashes based on value. In this example, I want the colors of the elements. Moss and snow are impostors.
# remove unique array of hashes based on a hash value
a = [
{ :color => "blue", :name => "water" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "green", :name => "earth" },
{ :color => "green", :name => "moss" },
{ :color => "white", :name => "snow" }
]
# remove moss and snow
uniques = []
a.each_with_index do |r, i|
colors = uniques.collect {|e| e[:color]}
if !colors.include? r[:color]
uniques.push r
else
a[i] = nil
end
end
a.compact!
puts a
This will print
{:color=>"blue", :name=>"water"}
{:color=>"red", :name=>"fire"}
{:color=>"white", :name=>"wind"}
{:color=>"green", :name=>"earth"}
Which is "correct" however I feel like this is excessive. My experience with .map .inject is limited and those advanced techniques elude me. If someone could re-factor this, it might help me understand another terse technique.
In Ruby 1.9, try the following
a.uniq! {|e| e[:color] }
I'd go with Array's reject or select methods:
require 'pp'
a = [
{ :color => "blue", :name => "water" },
{ :color => "red", :name => "fire" },
{ :color => "white", :name => "wind" },
{ :color => "green", :name => "earth" },
{ :color => "green", :name => "moss" },
{ :color => "white", :name => "snow" }
]
pp a.reject{ |h| %w[moss snow].include?( h[:name]) }
# >> [{:color=>"blue", :name=>"water"},
# >> {:color=>"red", :name=>"fire"},
# >> {:color=>"white", :name=>"wind"},
# >> {:color=>"green", :name=>"earth"}]
Alternately you can be positive about it and select the ones you want to keep:
pp a.select{ |h| %w[water fire wind earth].include?( h[:name] ) }
# >> [{:color=>"blue", :name=>"water"},
# >> {:color=>"red", :name=>"fire"},
# >> {:color=>"white", :name=>"wind"},
# >> {:color=>"green", :name=>"earth"}]
You're not really dealing with hashes, it's an array that happens to contain hashes, so don't let them confuse you. Array methods like reject and select are core methods for filtering out unwanted, or keeping wanted, elements.
In your code sample, you're losing sight of what your objective is: You want the elements, rejecting "moss" and "snow", which are non-elements. Filter out the non-elements, and you're left with the correct/real elements in the hashes. From there you can extract the correct colors.
An additional problem to watch out for with using uniq, is it is positional, in other words, it looks for the first unique value and rejects subsequent ones. This wasn't apparent in your code because your array was consistently the same order as you tested. If you shuffled the order though...:
2.times do
pp a.shuffle.uniq{ |h| h[:color] }
end
Pass #1...
# [{:color=>"red", :name=>"fire"},
# {:color=>"white", :name=>"wind"},
# {:color=>"green", :name=>"moss"},
# {:color=>"blue", :name=>"water"}]
Pass #2...
# [{:color=>"green", :name=>"earth"},
# {:color=>"blue", :name=>"water"},
# {:color=>"red", :name=>"fire"},
# {:color=>"white", :name=>"snow"}]
Suddenly we see that both "moss" and "snow" are sneaking into the results even though the colors are unique. Those are subtle gotcha's that you have to watch out for.
For anyone who might want an even shorter variant of the correct answer by Steve Wilhelm ,
BEWARE:
a.uniq!(&:color)
WILL NOT WORK for an array of hashes, just like
a[1].color
wouldn't work either.
For more information on the & operator, read this link, or the comments on this question which in turn have plenty of links to resources.
On the other hand, you could get the Symbol#to_proc method working using lambdas, as is explained here, though it could be just complicating things, and certainly would not be a shorter version of the correct answer. However, it is very interesting knowledge.
Thanks mukesh-kumar-gupta for the heads-up