I watched the video at https://gorails.com/blog/refactoring-if-statements but was looking for a more concise way of evading the use of multiple if or case statements.
The following works
def process(input)
commands = {
:q => Proc.new { puts "Goodbye" },
:tweet => Proc.new { puts "tweeting" },
:dm => Proc.new { puts "direct messaging"},
:help => Proc.new { puts "helping"}
}
commands[input.to_sym].call
end
process "tweet"
But how could i further shorten this ? I tried the following
def process(input)
commands = {
:q => { puts "Goodbye" },
:tweet => { puts "tweeting" },
:dm => { puts "direct messaging"},
:help => { puts "helping"}
}
commands[input.to_sym].to_proc.call
end
process "tweet"
but then i get the error
# syntax error, unexpected tSTRING_BEG, expecting keyword_do or '{' or '('
# :q => { puts "Goodbye" },
# ^
Any suggestions please ?
Use the lambda syntax
def process(input)
commands = {
:q => ->{ puts "Goodbye" },
:tweet => ->{ puts "tweeting" },
:dm => ->{ puts "direct messaging"},
:help => ->{ puts "helping"}
}
commands[input.to_sym].to_proc.call
end
process "tweet"
Using the new Hash syntax can shorten this further:
def process(input)
{
q: ->{ puts "Goodbye" },
tweet: ->{ puts "tweeting" },
dm: ->{ puts "direct messaging"},
help: ->{ puts "helping"}
}[input.to_sym].call
end
process "tweet"
Use Kernel#proc:
Equivalent to Proc.new
def process(input)
commands = {
:q => proc { puts "Goodbye" },
:tweet => proc { puts "tweeting" },
:dm => proc { puts "direct messaging"},
:help => proc { puts "helping"}
}[input.to_sym].call
end
I am not sure whether what you suggest or I will suggest here improves the elegance or the readability of the code under question but you could shorten it even further by using the hash accessor pattern as in:
def process(input)
commands = {
:q => Proc.new { puts "Goodbye" },
:tweet => Proc.new { puts "tweeting" },
:dm => Proc.new { puts "direct messaging"},
:help => Proc.new { puts "helping"}
}[input.to_sym].call
end
Related
I have a class method ::add_method(name, params = {}) that creates an instance method with define_method.
I need the parameters of the defined method to be keyword arguments depending on the params.
class Whatever
def self.add_method(name, params = {})
# do something with params
define_method name do |?|
# some business
end
end
end
The goal is that when the ::add_method is called with:
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
then it creates this method:
def hello(foo: 0, bar:)
# some business
end
Nota bene: this is not the real business, I've over simplified it so the question is easier to understand.
So as advised I went to class_eval.
class Whatever
class << self
def add_method(name, parameters = {})
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{name}(#{method_parameters(parameters)})
#{method_body(parameters)}
end
RUBY
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "foo: 0, bar:"
def method_parameters(parameters)
parameters.map do |key, options|
value = options[:required] ? '' : " #{options[:default] || 'nil'}"
"#{key}:#{value}"
end.join(', ')
end
# method_parameters({
# foo: { required: false, default: 0 },
# bar: { required: true }
# })
# => "[foo, bar]"
def method_body(parameters)
"[#{parameters.keys.map(&:to_s).join(', ')}]"
end
end
end
params = {
foo: { required: false, default: 0 },
bar: { required: true }
}
Whatever.add_method(:hello, params)
Whatever.new.hello(bar: true) # => [0, true]
Whatever.new.hello(foo: 42, bar: true) # => [42, true]
Whatever.new.hello # missing keyword: bar (ArgumentError)
I am trying to build a new_hash from this hash:
languages = {
:oo => {
:ruby => {
:type => "interpreted"
},
:javascript => {
:type => "interpreted"
},
:python => {
:type => "interpreted"
}
},
:functional => {
:clojure => {
:type => "compiled"
},
:erlang => {
:type => "compiled"
},
:javascript => {
:type => "interpreted"
}
}
}
and the desired result is:
{
:ruby => {
:type => "interpreted",
:style => [:oo]
},
:javascript => {
:type => "interpreted",
:style => [:oo, :functional]
},
:python => {
:type => "interpreted",
:style => [:oo]
},
:clojure => {
:type => "compiled",
:style => [:functional]
},
:erlang => {
:type => "compiled",
:style => [:functional]
}
}
Here is what I've done so far:
def reformat_languages(languages)
new_hash = {}
languages.each do |k, v|
v.each do |k1, v1|
new_hash[k1] = v1
new_hash[k1][:style] = []
new_hash[k1][:style] << k
end
end
new_hash
end
unfortunately, I cannot get the desired result. I understand that when the iteration arrives at the second javascript key, it re-writes over the first iteration giving me:
:javascript => {
:type => "interpreted",
:style => [:functional]
}
instead of:
:javascript => {
:type => "interpreted",
:style => [:oo, :functional]
}
Here is a link of a repl.it where I you can see the code in action: https://repl.it/BebC
I know I need to use a conditional, but I am not really sure where and on what to use it. If somebody could help me getting the desired result and explain a little bit why it works the way it works.
You can use something like
h = {}
languages.each do |k, v| # oo or func
v.each do |k1, v1| # ruby/python
if h[k1]
h[k1][:style] << k
else
h[k1] = {type: v1[:type], style: [k]}
end
end
end
It checks to see that h is defined, and if so, appends to its array. Otherwise it defines the entire hash with your type and a style array of size 1.
There is too much unconditional overwriting going on in your code. Should be something like this instead:
new_hash[k1] ||= {} # init to empty hash
new_hash[k1][:type] = v1[:type]
new_hash[k1][:style] ||= [] # make sure array exists
new_hash[k1][:style] << k
Instead of replacing entire new_hash[k1], you should change individual parts of it.
This is not an answer (so please no upvotes). Rather, it is an extended comment to help you understand the code #Martin suggested. (I see you are new to SO and quite possibly to Ruby as well.) Salting code with puts statements, as I have done, is often quite helpful, even after you become experienced with the language.
languages = {
:oo => {
:ruby => {
:type => "interpreted"
},
:javascript => {
:type => "interpreted"
}
},
:functional => {
:clojure => {
:type => "compiled"
},
:javascript => {
:type => "interpreted"
}
}
}
h = {}
languages.each do |k, v| # oo or func
puts "k=#{k}, v=#{v}"
v.each do |k1, v1| # ruby/python
puts " k1=#{k1}, v1=#{v1}"
if h[k1]
puts " h[#{k1}]=#{h[k1]} (truthy)"
h[k1][:style] << k
puts " h after h[#{k1}][:style] << #{k}: #{h}"
else
puts " h[#{k1}].nil?=true (falsy)"
h[k1] = {type: v1[:type], style: [k]}
puts " h after h[#{k1}] = {type: v1[:type], style: #{k}}: #{h}"
end
end
end
prints:
k=oo, v={:ruby=>{:type=>"interpreted"}, :javascript=>{:type=>"interpreted"}}
k1=ruby, v1={:type=>"interpreted"}
h[ruby].nil?=true (falsy)
h after h[ruby] = {type: v1[:type], :style: oo}:
{:ruby=>{:type=>"interpreted", :style=>[:oo]}}
k1=javascript, v1={:type=>"interpreted"}
h[javascript].nil?=true (falsy)
h after h[javascript] = {type: v1[:type], :style: oo}:
{:ruby=>{:type=>"interpreted", :style=>[:oo]},
:javascript=>{:type=>"interpreted", :style=>[:oo]}}
k=functional, v={:clojure=>{:type=>"compiled"}, :javascript=>{:type=>"interpreted"}}
k1=clojure, v1={:type=>"compiled"}
h[clojure].nil?=true (falsy)
h after h[clojure] = {type: v1[:type], :style: functional}:
{:ruby=>{:type=>"interpreted", :style=>[:oo]},
:javascript=>{:type=>"interpreted", :style=>[:oo]},
:clojure=>{:type=>"compiled", :style=>[:functional]}}
k1=javascript, v1={:type=>"interpreted"}
h[javascript]={:type=>"interpreted", :style=>[:oo]} (truthy)
h after h[javascript][:style] << functional:
{:ruby=>{:type=>"interpreted", :style=>[:oo]},
:javascript=>{:type=>"interpreted", :style=>[:oo, :functional]},
:clojure=>{:type=>"compiled", :style=>[:functional]}}
and returns:
#=> {:oo =>{:ruby=>{:type=>"interpreted"},
# :javascript=>{:type=>"interpreted"}},
# :functional=>{:clojure=>{:type=>"compiled"},
# :javascript=>{:type=>"interpreted"}}}
You are overwriting the hashes generated which is leading to the unexpected behavior you mentioned. The following piece of code does what you need. Its just a slightly modified version of your code.
def reformat_languages(languages)
new_hash = {}
languages.each do |k, v|
v.each do |k1, v1|
new_hash[k1] ||= v1 #ensures we do not overwrite the already generated language hash
new_hash[k1][:style] ||= [] #protecting against re-initialization of the style array
new_hash[k1][:style] << k
end
end
new_hash
end
I'm trying to use the values of other variable when declaring a ruby hash. Those values are now being escaped as I expected. How can I fix this?
variables
ipa_url, name, version and bundle-identifier
code
data = {
plist: {
dict: {
key: 'items',
array: {
dict: {
key: %w('assets','metadata'),
array: {
dict: [{ key: %w('kind','url'),
string: %w('software-package',
"#{ipa_url") },
{ key: %w('kind','url'),
string: %w('display-image',"#{icon_url.to_s}") },
{ key: %w('kind','url'),
string: %w('full-size-image',
"#{icon_url}") }],
dict: { key: %w('bundle-identifier','bundle-version',
'kind','title'),
string: %w("#{bundle-identifier}","#{version}",
'software',"#{name}")
}
}
}
}
}
}
}
The %w identifier is used to created an array out of a space delimited text:
%w(this is a test)
# => ["this", "is", "a", "test"]
If you want to use string interpolation there, you should use %W instead:
variable = 'test'
%W(this is a #{variable})
# => ["this", "is", "a", "test"]
zenspider goes over this in detail but for SO purposes, here's the breakdown:
%q(no interpolation)
[6] pry(main)> hey
=> "hello"
[7] pry(main)> hash = { 'hi' => %q("#{hey}", 'how are you') }
=> {"hi"=>"\"\#{hey}\", 'how are you'"}
%Q(interpolation and backslashes)
[8] pry(main)> hash = { 'hi' => %Q("#{hey}", 'how are you') }
=> {"hi"=>"\"hello\", 'how are you'"}
%(interpolation and backslashes)
[9] pry(main)> hash = { 'hi' => %("#{hey}", 'how are you') }
=> {"hi"=>"\"hello\", 'how are you'"}
%W(interpolation) as Uri showed:
[7] pry(main)> hash = { 'hi' => %W(#{hey} how are you) }
=> {"hi"=>["hello", "how", "are", "you"]}
I have this array:
params[:types] = [type1, type2, type3...]
I would like to populate my hash the following way using the above array:
params[:hash] = {
"type1" => {
something: something
},
"type2" => {
something: something
},
}
Using a for loop like for index in i ...params[:types] just populates the hash with the last value in the array.
You can use the each_with_object method to do this:
params = {}
params[:types] = ["type1", "type2", "type3"]
params[:types].each_with_object({}) { |k, h| h[k] = { "something" => "something" } }
That last line will return:
=> {"type1"=>{"something"=>"something"}, "type2"=>{"something"=>"something"}, "type3"=>{"something"=>"something"}}
Here is a code snippet example that does what you need.
hash = {}
array.each do |a|
hash[a.to_s] = { "something" => "something" }
end
output:
hash
=> {
"type1" => {
"something" => "something"
},
"type2" => {
"something" => "something"
},
"type3" => {
"something" => "something"
}
}
You could do this:
params = { types: ["type1", "type2", "type3"] }
Hash[params[:types].product([{"something" => "something"}])]
#=> {"type1"=>{"something"=>"something"},
# "type2"=>{"something"=>"something"},
# "type3"=>{"something"=>"something"}}
or with Ruby 2.1,
params[:types].product([{"something" => "something"}]).to_h
If you want a different hash for each element of params[:types]:
hashes = [{ "something1"=>"something1" }, { "something2"=>"something2" },
{ "something3"=>"something3" }]
then
Hash[params[:types].zip(hashes)]
#=> {"type1"=>{"something1"=>"something1"},
# "type2"=>{"something2"=>"something2"},
# "type3"=>{"something3"=>"something3"}}
I'm using the following code to generate a JSON file containing all category information for a particular website.
The goal is to have a JSON file with the following format:
[
{
"id":"36_17",
"name":"Diversen Particulier",
"group":"Diversen",
"search_attributes":{
"0":"Prijs van/tot",
"1":"Groep en Rubriek",
"2":"Conditie",
}
},
{
"id":"36_18",
"name":"Diversen Zakelijk",
"group":"Diversen",
"search_attributes":{
"0":"Prijs van/tot",
"1":"Groep en Rubriek",
"2":"Conditie",
}
},
{
"id":"36_19",
"name":"Overige Diversen",
"group":"Diversen",
"search_attributes":{
"0":"Prijs van/tot",
"1":"Groep en Rubriek",
"2":"Conditie",
}
}, {...}
]
But I keep getting this format:
[
{
"id":"36_17",
"name":"Diversen Particulier",
"group":"Diversen",
"search_attributes":{"0":"Prijs van/tot"}
},
{
"id":"36_17",
"name":"Diversen Particulier",
"group":"Diversen",
"search_attributes":{"1":"Groep en Rubriek"}
},
{
"id":"36_17",
"name":"Diversen Particulier",
"group":"Diversen",
"search_attributes":{"2":"Conditie"}
}, {...}
]
The search_attributes are not getting saved correctly.
I'm using the following code:
require 'mechanize'
#hashes = []
# Initialize Mechanize object
a = Mechanize.new
# Begin scraping
a.get('http://www.marktplaats.nl/') do |page|
groups = page.search('//*[(#id = "navigation-categories")]//a')
groups.each_with_index do |group, index_1|
a.get(group[:href]) do |page_2|
categories = page_2.search('//*[(#id = "category-browser")]//a')
categories.each_with_index do |category, index_2|
a.get(category[:href]) do |page_3|
search_attributes = page_3.search('//*[contains(concat( " ", #class, " " ), concat( " ", "heading", " " ))]')
search_attributes.each_with_index do |attribute, index_3|
item = {
id: "#{index_1}_#{index_2}",
name: category.text,
group: group.text,
:search_attributes => {
:index_3.to_s => "#{attribute.text unless attribute.text == 'Outlet '}"
}
}
#hashes << item
puts item
end
end
end
end
end
end
# Open file and begin
File.open("json/light/#{Time.now.strftime '%Y%m%d%H%M%S'}_light_categories.json", 'w') do |f|
puts '# Writing category data to JSON file'
f.write(#hashes.to_json)
puts "|-----------> Done. #{#hashes.length} written."
end
puts '# Finished.'
The question is what's causing this and how do I solve it?
Update
A big thanks to arie-shaw for his answer.
Here's the working code:
require 'mechanize'
#hashes = []
# Initialize Mechanize object
a = Mechanize.new
# Begin scraping
a.get('http://www.marktplaats.nl/') do |page|
groups = page.search('//*[(#id = "navigation-categories")]//a')
groups.each_with_index do |group, index_1|
a.get(group[:href]) do |page_2|
categories = page_2.search('//*[(#id = "category-browser")]//a')
categories.each_with_index do |category, index_2|
a.get(category[:href]) do |page_3|
search_attributes = page_3.search('//*[contains(concat( " ", #class, " " ), concat( " ", "heading", " " ))]')
attributes_hash = {}
search_attributes.each_with_index do |attribute, index_3|
attributes_hash[index_3.to_s] = "#{attribute.text unless attribute.text == 'Outlet '}"
end
item = {
id: "#{index_1}.#{index_2}",
name: category.text,
group: group.text,
:search_attributes => attributes_hash
}
#hashes << item
puts item
end
end
end
end
end
# Open file and begin
File.open("json/light/#{Time.now.strftime '%Y%m%d%H%M%S'}_light_categories.json", 'w') do |f|
puts '# Writing category data to JSON file'
f.write(#hashes.to_json)
puts "|-----------> Done. #{#hashes.length} written."
end
puts '# Finished.'
The most inner each_with_index should be only be used to generate the search_attributes hash, rather than an element hash of the top level array in the result.
# Begin scraping
a.get('http://www.marktplaats.nl/') do |page|
groups = page.search('//*[(#id = "navigation-categories")]//a')
groups.each_with_index do |group, index_1|
a.get(group[:href]) do |page_2|
categories = page_2.search('//*[(#id = "category-browser")]//a')
categories.each_with_index do |category, index_2|
a.get(category[:href]) do |page_3|
search_attributes = page_3.search('//*[contains(concat( " ", #class, " " ), concat( " ", "heading", " " ))]')
attributes_hash = {}
search_attributes.each_with_index do |attribute, index_3|
attributes_hash[index_3.to_s] = "#{attribute.text unless attribute.text == 'Outlet '}"
end
#hashes << {
id: "#{index_1}_#{index_2}",
name: category.text,
group: group.text,
search_attributes: attributes_hash
}
end
end
end
end
end