I wrote a small Ruby program, but can't access the hash value stored in the parent class.
Here is the code:
class College
##dep = ["cs" => 60000, "mat" => 20000, "che" => 30000]
end
class Student < College
def get_det
puts "Enter name... \n"
#name = gets
puts "Enter department...\n"
#dpt = gets
end
def set_fee
case #dpt
when "cs"
#fee = (##dep["cs"]).to_i
when "mat"
#fee = ##dep["mat"].to_i
when "che"
#fee = ##dep["che"].to_i
else
puts "Eror!!!"
end
end
def print_det
puts "Name : #{#name}"
puts "Department : #{#dpt}"
puts "Course fee : #{#fee}"
end
end
det = Student.new
det.get_det
det.set_fee
det.print_det
I got the output as:
Output:
You've defined your ##dep variable as an array, not as a hash. You need to replace [ ] with { }, like so:
##dep = {"cs" => 60000, "mat" => 20000, "che" => 30000}
Then you'll be able to access your hash values via the string keys:
##dep['cs'] # Will return 6000
And just an FYI, your set_fee method could be refactored to just be this:
def set_fee
#fee = ##dep[#dpt] || 'Error!'
puts #fee
end
Since you're simply passing in the value you're checking against for each of your when statements, you can just pass the value directly to your ##dep object. And you don't need to_i, because the values in your hash are already integers.
Related
Trying to write a program interactively which can take inputs from command line as an expression or attributes like -
irb : 3+2
Should evaluate to => 5
Attribute
irb : abc = 1
=> 1
irb : jkl(or def) = 1
=> 1
irb : abc + def
=> 2
Also the evaluation should take place once user inputs blank line.
My efforts : I created a method attr_accessor which iterates through the array of *secret passed to it, and calls define_method on each attr, creating an instance variable getter and setter for each attribute.
Part of code working :
I made a success in evaluating the expressions and returning string values.
irb : 3+2
Should evaluate to => 5
irb : True
=> True
But still stuck with evaluation of assignment to attributes and unable to dynamically store those values in my interactive irb. Below expected results are not working :
Attribute
irb : abc = 1
=> 1
irb : def = 1
=> 1
irb : abc + def
=> 2
Note - I don't want to use "require 'irb' " or " "require 'pry'". Can this be achieved with simple ruby code ?
My Solution:
class Demo
def self.attr_accessor(*secret)
secret.each do |attr|
define_method(attr) { instance_variable_get("##{attr}") }
define_method("#{attr}=") { |val| instance_variable_set("##{attr}", val) }
end
get_binding
end
def self.method_new(input)
#object = attr_accessor(input)
end
def self.method(secret)
#object = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.simple_method(secret)
#object = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /^.*=.*$/
b2 = Demo.method_new(*user_input)
puts eval('#object', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object', b3)
else
b4 = Demo.simple_method(user_input)
puts eval('#object', b4)
end
end
Expected Result:
irb : 3+2
#note - each result evaluated after user enters blank line
Should evaluate to => 5
Attributes ---
irb : abc = 1
#note - each result evaluated after user enters blank line
=> 1
irb : def = 1
#note - each result evaluated after user enters blank line
=> 1
irb : abc + def( or jkl)
#note - each result evaluated after user enters blank line
=> 2
Actual Result : Output is "Invalid expression" for all other inputs except expressions and simple strings.
I believe, I have partly reached to the solution of above problem. Now I can store the values of attributes in a hash map. I tried accessing these values through keys and thus can easily store and display values for assignments like:
rb : x = 1
=> 1
or
rb : y = 1
But the part of code I have written for evaluating 'x + y' is trying to partition it on operator and then accessing value of each attribute.
I am doing something wrong in line of code marked with comment #faulty. Due to which I got output like
=> x y
I am unable to access key values after partitioning.
Can someone please advise on this piece of code alone ?
Solution:
class Module
def initialize(args)
args.each do |key, value|
# the instance_variable_set method dynamically creates instance variables
# with the key as the name and value as the assigned value
instance_variable_set("##{key}",value)
# define_singleton_method creates a getter method with the same name as the
# key and inside the block you define what it returns
define_singleton_method(key){ value }
#defining the setter method
define_singleton_method("#{key}=") do |val|
instance_variable_set("##{key}", val)
end
end
end
end
class Demo
#var :bar
def self.eval_binary_expr(expr)
if expr =~ /^.*=.*$/
obj = Module.new(:name => expr)
#object1 = eval(obj.name)
get_binding
else
obj = Module.new(:name => expr)
l_operand, op, r_operand = (obj.name).partition(%r{[/*+-]}) #Faulty
if op.empty?
raise ArgumentError, "Invalid operation or no operation in expression: #{expr}"
end
case op
when '/'; then #object1 = (l_operand / r_operand); get_binding
when '*'; then #object1 = (l_operand * r_operand); get_binding
when '+'; then #object1 = (l_operand + r_operand); get_binding
when '-'; then #object1 = (l_operand - r_operand); get_binding
end
end
end
def self.method(secret)
#object2 = Regexp.new(/\A[\d+\-*\/=. ]+\z/).match(secret.to_s) ? eval(secret) : "Invalid expression"
get_binding
end
def self.new_method(secret)
#object3 = secret
get_binding
end
def self.get_binding
binding
end
end
user_input = ''
until user_input == 'q' do
user_input = gets.chomp
if user_input =~ /\A[\w+\-*\/=. ]+\z/
b2 = Demo.eval_binary_expr(user_input)
puts eval('#object1', b2)
elsif user_input =~ /\A[\d+\-*\/=. ]+\z/
b3 = Demo.method(user_input)
puts eval('#object2', b3)
else
b4 = Demo.new_method(user_input)
puts eval('#object3', b4)
end
end
Read a csv format file and construct a new class with the name of the file dynamically. So if the csv is persons.csv, the ruby class should be person, if it's places.csv, the ruby class should be places
Also create methods for reading and displaying each value in "csv" file and values in first row of csv file will act as name of the function.
Construct an array of objects and associate each object with the row of a csv file. For example the content of the csv file could be
name,age,city
abd,45,TUY
kjh,65,HJK
Previous code :
require 'csv'
class Feed
def initialize(source_name, column_names = [])
if column_names.empty?
column_names = CSV.open(source_name, 'r', &:first)
end
columns = column_names.reduce({}) { |columns, col_name| columns[col_name] = []; columns }
define_singleton_method(:columns) { column_names }
column_names.each do |col_name|
define_singleton_method(col_name.to_sym) { columns[col_name] }
end
CSV.foreach(source_name, headers: true) do |row|
column_names.each do |col_name|
columns[col_name] << row[col_name]
end
end
end
end
feed = Feed.new('input.csv')
puts feed.columns #["name", "age", "city"]
puts feed.name # ["abd", "kjh"]
puts feed.age # ["45", "65"]
puts feed.city # ["TUY", "HJK"]
I am trying to refine this solution using class methods and split code into smaller methods. Calling values outside the class using key names but facing errors like "undefined method `age' for Feed:Class". Is that a way I can access values outside the class ?
My solution looks like -
require 'csv'
class Feed
attr_accessor :column_names
def self.col_name(source_name, column_names = [])
if column_names.empty?
#column_names = CSV.open(source_name, :headers => true)
end
columns = #column_names.reduce({}) { |columns, col_name| columns[col_name] = []; columns }
end
def self.get_rows(source_name)
col_name(source_name, column_names = [])
define_singleton_method(:columns) { column_names }
column_names.each do |col_name|
define_singleton_method(col_name.to_sym) { columns[col_name] }
end
CSV.foreach(source_name, headers: true) do |row|
#column_names.each do |col_name|
columns[col_name] << row[col_name]
end
end
end
end
obj = Feed.new
Feed.get_rows('Input.csv')
puts obj.class.columns
puts obj.class.name
puts obj.class.age
puts obj.class.city
Expected Result -
input = Input.new
p input.name # ["abd", "kjh"]
p input.age # ["45", "65"]
input.name ='XYZ' # Value must be appended to array
input.age = 25
p input.name # ["abd", "kjh", "XYZ"]
p input.age # ["45", "65", "25"]
Let's create the CSV file.
str =<<END
name,age,city
abd,45,TUY
kjh,65,HJK
END
FName = 'temp/persons.csv'
File.write(FName, str)
#=> 36
Now let's create a class:
klass = Class.new
#=> #<Class:0x000057d0519de8a0>
and name it:
class_name = File.basename(FName, ".csv").capitalize
#=> "Persons"
Object.const_set(class_name, klass)
#=> Persons
Persons.class
#=> Class
See File::basename, String#capitalize and Module#const_set.
Next read the CSV file with headers into a CSV::Table object:
require 'csv'
csv = CSV.read(FName, headers: true)
#=> #<CSV::Table mode:col_or_row row_count:3>
csv.class
#=> CSV::Table
See CSV#read. We may now create the methods name, age and city.
csv.headers.each { |header| klass.define_method(header) { csv[header] } }
See CSV#headers, Module::define_method and CSV::Row#[].
We can now confirm they work as intended:
k = klass.new
k.name
#=> ["abd", "kjh"]
k.age
#=> ["45", "65"]
k.city
#=> ["TUY", "HJK"]
or
p = Persons.new
#=> #<Persons:0x0000598dc6b01640>
p.name
#=> ["abd", "kjh"]
and so on.
My project reads many files (these files have title text and sections) and should find the title of the files that contain an acronym. This is my docs class:
class Doc
def initialize(id, secciones)
#id, #secciones = id, secciones
end
def to_s
result = "" + #id.to_s + "\n" + #secciones.to_s
return result
end
def tiene_acronimo(acr)
puts "a ver si tiene acronimos el docu.."
tiene_acronimo = false
secciones.each do |seccion|
if seccion.tiene_acronimo(acr)
tiene_acronimo = true
end
end
return tiene_acronimo
end
attr_accessor :id
attr_accessor :secciones
end
And this my sections class:
class Section
def initialize ()
#title = ""
#text = ""
end
def tiene_acronimo(acr)
return title.include?(acr) || text.include?(acr)
end
end
And this my method:
def test()
results = Array.new
puts "Dame el acronimo"
acr = gets
documentos_cientificos.each do |d|
if d.tiene_acronimo(acr)
results << d
end
end
The method gets an acronym and should find all documents that contain it. The method inclue? [sic] ingores the upcase and returns true if the docs contain any substring like the acronym. For example:
Multiple sclerosis (**MS**), also known as # => `true`
Presenting signs and sympto**ms** # => `false` (but `include?` returns `true`)
How I can find an acronym more easily?
You could use some regex with the match function. The following regex will find a match if the content contains the FULL word provided. It will ignore substrings, and it will be case sensitive.
arc = "MS"
title = "Multiple sclerosis (MS), also known as"
text = "Presenting signs and symptoms"
title.match(/\b#{Regexp.escape(acr)}\b/) # => #<MatchData "MS">
text.match(/\b#{Regexp.escape(acr)}\b/) # => nil
or equivalently
title.match(/\b#{Regexp.escape(acr)}\b/).to_a.size > 0 # => true
text.match(/\b#{Regexp.escape(acr)}\b/).to_a.size > 0 # => false
...so you could redefine your function as:
def tiene_acronimo(acr)
regex_to_match = /\b#{Regexp.escape(acr)}\b/
has_acr = false
if (title.match(regex_to_match)) || (text.match(regex_to_match))
has_acr = true
end
return has_acr
end
PStore is a data-serializer in the Stdlib which is human-readable like JSON or YAML.
Everything has to be done in a "transaction", e.g.
storage = PStore.new("db")
storage.transaction do |db|
db[:key] = {}
db[:key][:sub_key] = "val"
return db[:key][:sub_key]
end
# ==> "val"
I want to be able to make a method like this:
def store(storage, list_of_keys, value)
storage.transaction do |db|
db[list_of_keys[0][list_of_keys[1]][list_of_keys[n]] = value
end
end
Conceptually I think currying would be useful here, but I've only done it in Javascript.
It doesn't seem to work to store the references to the intermediate values in variables like this:
storage.transaction do |db|
db_val_1 = db[keys[0]]
db_val_1 = value
end
I don't know exactly what you want. But my guess is you wanna store a value inside a nested hash with given list of keys. Since it's a nested has, I would use, well, a hash obviously. Here is a full working code for what I think you are trying to do:
require 'pstore'
require 'minitest/autorun'
storage = PStore.new("data_file.pstore")
def nest(key_list, value)
reverse_key_list = key_list.reverse
inner_most = {"#{reverse_key_list.shift}" => value}
reverse_key_list.reduce(inner_most) {|acc, current| {"#{current}" => acc }}
end
def nested_store(storage, key_list, value)
root = key_list.shift
data = nest(key_list, value)
storage.transaction { storage[root] = data }
end
describe "pstore test" do
before do
#key_list = %w(root sub1 sub2 sub3)
#val = "value"
end
it "should nest the list" do
nest(#key_list, #val).must_equal({"root" => {"sub1" => {"sub2" => {"sub3" => "value" }}}})
end
it "store the nested list" do
storage = PStore.new("data_file.pstore")
nested_store(storage, #key_list, #val)
storage.transaction do
storage["root"]["sub1"]["sub2"]["sub3"].must_equal "value"
end
end
end
I am trying to compose an object Transaction from objects TranFee and Rate.
class Transaction
attr_reader :tranfee, :rate
def initialize(hash)
#tranfee = PaymentType::TranFee.new(hash)
#rate = PaymentType::Rate.new(hash)
end
end
module PaymentType
def initialize(args = {}, regex)
args.each do |key,value|
if key =~ regex
instance_variable_set("##{key}", value) unless value.nil?
eigenclass = class << self; self; end
eigenclass.class_eval do
attr_reader key
end
end
end
end
class TranFee
include PaymentType
def initialize(args, regex = /\Atran.*/)
super(args, regex)
end
end
class Rate
include PaymentType
def initialize(args, regex = /\Arate.*/)
super(args, regex)
end
end
end
The rate and TranFee objects are created from a hash like the one below.
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005,
"tran_fee" => 0.21, "rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
I am initializing the objects based on regex because the hash will eventually contain more values and I want the program to adjust as more items/classes are added.
Additionally there will be some instances where there are no key's starting with "tran". Does anyone know how to make Transaction create only a Rate object if TranFee has no instance variables inside of it? (in otherwords, if the hash returns nothing when keys =~ /\Atran.*/)
an example would be when the hash looks like this reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "rate_basis_points" => 0.002}, right now the output is
#<Transaction:0x007ff98c070548 #tranfee=#<PaymentType::TranFee:0x007ff98c070520>, #rate=#<PaymentType::Rate:0x007ff98c0704a8 #rate_base=0.0005, #rate_basis_points=0.002>>
So I am getting a TranFee object with nothing in it and I would like for that to drop off in this situation. not sure if there may be a better way to design this? I was trying to think of a way to use ostruct or struct, but I havnt been able to figure it out. thanks for any help here.
I believe your strategy is very problematic - creating attributes to a class from user input doesn't sound like a very good idea.
Furthermore, adding methods (like attr_reader) to every instances can have severe performance issues.
If all you want is a data structure to hold your data, keep using a Hash. If you want a structure you can query using a dot notation instead of bracket notation, you might want to consider a gem like hashie or hashr.
If you want some code to make the flat data-structure hierarchical, I can suggest something like this:
hierarchical_hash = hash.each_with_object({}) do |(k, v), h|
if k.match(/^([^_]+)_(.+)$/)
root_key = $1
child_key = $2
h[root_key] ||= {}
h[root_key][child_key] = v
else
h[k] = v
end
end
# => {
# => "name" => "reg_debit",
# => "rate" => {
# => "base" => 0.0005,
# => "basis_points" => 0.002
# => },
# => "tran" => {
# => "fee" => 0.21,
# => "auth_fee" => 0.1
# => }
# => }
Your question raises some interesting issues. I will try to explain how you can fix it, but, as #Uri mentions, there may be better ways to address your problem.
I've assumed #tranfee is to be set equal to the first value in the hash whose key begins with "tran" and that #rate is to be set equal to the first value in the hash whose key begins with "rate". If that interpretation is not correct, please let me know.
Note that I've put initialize in the PaymentType module in a class (Papa) and made TranFee and Rate subclasses. That's the only way you can use super within initialize in the subclasses of that class.
Code
class Transaction
attr_reader :tranfee, :rate
def initialize(hash={})
o = PaymentType::TranFee.new(hash)
#tranfee = o.instance_variable_get(o.instance_variables.first)
o = PaymentType::Rate.new(hash)
#rate = o.instance_variable_get(o.instance_variables.first)
end
end
.
module PaymentType
class Papa
def initialize(hash, prefix)
key, value = hash.find { |key,value| key.start_with?(prefix) && value }
(raise ArgumentError, "No key beginning with #{prefix}") unless key
instance_variable_set("##{key}", value)
self.class.singleton_class.class_eval { attr_reader key }
end
end
class TranFee < Papa
def initialize(hash)
super hash, "tran"
end
end
class Rate < Papa
def initialize(hash)
super hash, "rate"
end
end
end
I believe the method Object#singleton_class has been available since Ruby 1.9.3.
Example
reg_debit = {"name" => "reg_debit", "rate_base" => 0.0005, "tran_fee" => 0.21,
"rate_basis_points" => 0.002, "tran_auth_fee" => 0.10}
a = Transaction.new reg_debit
p Transaction.instance_methods(false) #=> [:tranfee, :rate]
p a.instance_variables #=> [:#tranfee, :#rate]
p a.tranfee #=> 0.21
p a.rate #=> 0.0005