How to refer to a variable via a string containing its name? - ruby

I'd like to classify the projects by searching keywords. For each category of project type, I have an array of key words. I will use the variable name as the project type and the contents of the variable as keywords for matching.
project_detail = "Build a school"
a = %w[education school]
b = ["Health Care", "Clean Water"]
projects = %w[a b]
project_type = String.new
projects.each do |e|
# How to refer to the variable a and b? (I used one of the answers)
eval(e).each do |keyword|
project_type = e if project_detail.match(/#{keyword}/)
end
end

You could use any of the below:
1. eval
a = 1
b = 2
c = 3
vars = %w[a b c]
vars.each { |v| puts eval(v) }
Output:
1
2
3
=> ["a", "b", "c"]
2. Binding#local_variable_get
vars.each { |v| puts binding.local_variable_get(v) }

I will use the variable name as the project type ...
Don't use variable names to represent data. Variables should refer to data, not be that data. Here's how I would approach it.
First of all, I would create a small class for project types. The class should have two attributes, name and keywords. This can be done using the class keyword:
class ProjectType
attr_accessor :name, :keywords
def initialize(name, keywords)
#name = name
#keywords = keywords
end
end
or via Struct:
ProjectType = Struct.new(:name, :keywords)
Then, I would create an array of all project types:
#project_types = [
ProjectType.new('a', %w[education school]),
ProjectType.new('b', ['Health Care', 'Clean Water'])
]
Now, we need a way to find a project by keyword. I would wrap that in a method:
def find_project_type(name)
#project_types.find do |prj|
Regexp.union(prj.keywords) =~ name
end
end
find traverses the #project_types array, returning the first element for which the block returns true. Within the block, we use Regexp.union to build a regexp matching all of the project's keywords and =~ to perform the match.
The method can now be used like this:
project_type = find_project_type("Build a school")
#=> #<struct ProjectType name="a", keywords=["education", "school"]>
The project type's name can be retrieved via:
project_type.name
#=> "a"

I guess you mean that you want to use the variable e to get to the contents of, say, a.
In this case, you could use eval(e), because e is of class String.
Of course the question is why you want to access a variable via a string holding its name .....

Related

Why am I getting 4 back when I should get an array in ruby

I have the following in a variable called store
#<InitializeStore:0x007f83a39b72a0
#inventory=[
#<CreateStoreInventory:0x007f83a39b7228 #item="apples", #value=150>,
#<CreateStoreInventory:0x007f83a39b71d8 #item="bananas", #value=350>,
#<CreateStoreInventory:0x007f83a39b7188 #item="tissue paper", #value=450>,
#<CreateStoreInventory:0x007f83a39b7138 #item="soap", #value=850>
]>
Now when I try and do: store.inventory I should get an array. Instead I get everything inside the array spit out 4 times. This is not what I want. I want to be able to do store.inventory and get the array of objects.
I thought of assigning store.inventory to an inventory array, but it's already an array ...
Ideas?
It depends on how your InitializeStore is implemented.
store.inventory is actually store.inventory(). It's not a field access, it is a method call.
Its result depends on that method (inventory) is implemented.
Your code works perfectly for me:
require 'pp'
class InitializeStore
attr_reader :inventory
def initialize(arr)
#inventory = arr
end
end
class CreateStoreInventory
def initialize(item, value)
#item = item
#value = value
end
end
store_inventories = [
CreateStoreInventory.new('apples', 150),
CreateStoreInventory.new('bananas', 350),
CreateStoreInventory.new('tissue paper', 450),
CreateStoreInventory.new('soap', 850),
]
my_store = InitializeStore.new(store_inventories)
pp my_store.inventory
--output:--
[#<CreateStoreInventory:0x000001008423e8 #item="apples", #value=150>,
#<CreateStoreInventory:0x00000100842398 #item="bananas", #value=350>,
#<CreateStoreInventory:0x00000100842348 #item="tissue paper", #value=450>,
#<CreateStoreInventory:0x00000100842258 #item="soap", #value=850>]
1) Oh, but you didn't post your code. It's sort of hard to debug imaginary code.
2) Your class names are all wrong. Class names should not have verbs in them, e.g. Initialize, Create. Class names are things, which are nouns, e.g.:
require 'pp'
class Store
attr_reader :inventory
def initialize(arr)
#inventory = arr
end
end
class Product
def initialize(name, price)
#name = name
#price = price
end
end
products = [
Product.new('apples', 150),
Product.new('bananas', 350),
Product.new('tissue paper', 450),
Product.new('soap', 850),
]
my_store = Store.new(products)
pp my_store.inventory
--output:--
[#<Product:0x00000100842438 #name="apples", #price=150>,
#<Product:0x000001008423e8 #name="bananas", #price=350>,
#<Product:0x00000100842398 #name="tissue paper", #price=450>,
#<Product:0x00000100842348 #name="soap", #price=850>]
3) puts <--> p <--> pp
puts arr: prints out a string representation of each element of the array--followed by a newline. If the element is a string, and it already ends in a newline, puts doesn't add another newline.
p arr: prints a string representation of the entire array, which just happens to look like an array you specify in your code, e.g. [1, 2, 3], with quotes around it.
pp arr: pretty print the array. Like p but adds some formatting to make the output easier to read.

Ruby regex into array of hashes but need to drop a key/val pair

I'm trying to parse a file containing a name followed by a hierarchy path. I want to take the named regex matches, turn them into Hash keys, and store the match as a hash. Each hash will get pushed to an array (so I'll end up with an array of hashes after parsing the entire file. This part of the code is working except now I need to handle bad paths with duplicated hierarchy (top_* is always the top level). It appears that if I'm using named backreferences in Ruby I need to name all of the backreferences. I have gotten the match working in Rubular but now I have the p1 backreference in my resultant hash.
Question: What's the easiest way to not include the p1 key/value pair in the hash? My method is used in other places so we can't assume that p1 always exists. Am I stuck with dropping each key/value pair in the array after calling the s_ary_to_hash method?
NOTE: I'm keeping this question to try and solve the specific issue of ignoring certain hash keys in my method. The regex issue is now in this ticket: Ruby regex - using optional named backreferences
UPDATE: Regex issue is solved, the hier is now always stored in the named 'hier' group. The only item remaining is to figure out how to drop the 'p1' key/value if it exists prior to creating the Hash.
Example file:
name1 top_cat/mouse/dog/top_cat/mouse/dog/elephant/horse
new12 top_ab12/hat[1]/top_ab12/hat[1]/path0_top_ab12/top_ab12path1/cool
tops top_bat/car[0]
ab123 top_2/top_1/top_3/top_4/top_2/top_1/top_3/top_4/dog
Expected output:
[{:name => "name1", :hier => "top_cat/mouse/dog/elephant/horse"},
{:name => "new12", :hier => "top_ab12/hat[1]/path0_top_ab12/top_ab12path1/cool"},
{:name => "tops", :hier => "top_bat/car[0]"},
{:name => "ab123", :hier => "top_2/top_1/top_3/top_4/dog"}]
Code snippet:
def s_ary_to_hash(ary, regex)
retary = Array.new
ary.each {|x| (retary << Hash[regex.match(x).names.map{|key| key.to_sym}.zip(regex.match(x).captures)]) if regex.match(x)}
return retary
end
regex = %r{(?<name>\w+) (?<p1>[\w\/\[\]]+)?(?<hier>(\k<p1>.*)|((?<= ).*$))}
h_ary = s_ary_to_hash(File.readlines(filename), regex)
What about this regex ?
^(?<name>\S+)\s+(?<p1>top_.+?)(?:\/(?<hier>\k<p1>(?:\[.+?\])?.+))?$
Demo
http://rubular.com/r/awEP9Mz1kB
Sample code
def s_ary_to_hash(ary, regex, mappings)
retary = Array.new
for item in ary
tmp = regex.match(item)
if tmp then
hash = Hash.new
retary.push(hash)
mappings.each { |mapping|
mapping.map { |key, groups|
for group in group
if tmp[group] then
hash[key] = tmp[group]
break
end
end
}
}
end
end
return retary
end
regex = %r{^(?<name>\S+)\s+(?<p1>top_.+?)(?:\/(?<hier>\k<p1>(?:\[.+?\])?.+))?$}
h_ary = s_ary_to_hash(
File.readlines(filename),
regex,
[
{:name => ['name']},
{:hier => ['hier','p1']}
]
)
puts h_ary
Output
{:name=>"name1", :hier=>"top_cat/mouse/dog/elephant/horse\r"}
{:name=>"new12", :hier=>"top_ab12/hat[1]/path0_top_ab12/top_ab12path1/cool\r"}
{:name=>"tops", :hier=>"top_bat/car[0]"}
Discussion
Since Ruby 2.0.0 doesn't support branch reset, I have built a solution that add some more power to the s_ary_to_hash function. It now admits a third parameter indicating how to build the final array of hashes.
This third parameter is an array of hashes. Each hash in this array has one key (K) corresponding to the key in the final array of hashes. K is associated with an array containing the named group to use from the passed regex (second parameter of s_ary_to_hash function).
If a group equals nil, s_ary_to_hash skips it for the next group.
If all groups equal nil, K is not pushed on the final array of hashes.
Feel free to modify s_ary_to_hash if this isn't a desired behavior.
Edit: I've changed the method s_ary_to_hash to conform with what I now understand to be the criterion for excluding directories, namely, directory d is to be excluded if there is a downstream directory with the same name, or the same name followed by a non-negative integer in brackets. I've applied that to all directories, though I made have misunderstood the question; perhaps it should apply to the first.
data =<<THE_END
name1 top_cat/mouse/dog/top_cat/mouse/dog/elephant/horse
new12 top_ab12/hat/top_ab12/hat[1]/path0_top_ab12/top_ab12path1/cool
tops top_bat/car[0]
ab123 top_2/top_1/top_3/top_4/top_2/top_1/top_3/top_4/dog
THE_END
text = data.split("\n")
def s_ary_to_hash(ary)
ary.map do |s|
name, _, downstream_path = s.partition(' ').map(&:strip)
arr = []
downstream_dirs = downstream_path.split('/')
downstream_dirs.each {|d| puts "'#{d}'"}
while downstream_dirs.any? do
dir = downstream_dirs.shift
arr << dir unless downstream_dirs.any? { |d|
d == dir || d =~ /#{dir}\[\d+\]/ }
end
{ name: name, hier: arr.join('/') }
end
end
s_ary_to_hash(text)
# => [{:name=>"name1", :hier=>"top_cat/mouse/dog/elephant/horse"},
# {:name=>"new12", :hier=>"top_ab12/hat[1]/path0_top_ab12/top_ab12path1/cool"},
# {:name=>"tops", :hier=>"top_bat/car[0]"},
# {:name=>"ab123", :hier=>"top_2/top_1/top_3/top_4/dog"}]
The exclusion criterion is implement in downstream_dirs.any? { |d| d == dir || d =~ /#{dir}\[\d+\]/ }, where dir is the directory that is being tested and downstream_dirs is an array of all the downstream directories. (When dir is the last directory, downstream_dirs is empty.) Localizing it in this way makes it easy to test and change the exclusion criterion. You could shorten this to a single regex and/or make it a method:
dir exclude_dir?(dir, downstream_dirs)
downstream_dirs.any? { |d| d == dir || d =~ /#{dir}\[\d+\]/ }end
end
Here is a non regexp solution:
result = string.each_line.map do |line|
name, path = line.split(' ')
path = path.split('/')
last_occur_of_root = path.rindex(path.first)
path = path[last_occur_of_root..-1]
{name: name, heir: path.join('/')}
end

Ruby assignment behavior

please help find some article of the next behavior.
a = 'qwer'
a = b
b << 'ty'
puts b # => 'qwerty'
puts a # => 'qwerty'
but if
a = 'qwer'
a = b
b = 'ty'
puts b # => 'ty'
puts a # => 'qwer'
I know why in this case
I know that it works well, but I can not find an explanation - why so
P.S.
if applicable - please give the links to the articles on this subject (or similar Maybe i miss more interesting feature like this).
Thn.
When you do
a = b
you make variable a keep reference to the same object as variable b. That's why when you type:
b << 'ty'
string contained in variable a will also change - this is the same String instance.
On the other hand, let's say you have variable b containing reference to string 'qwer'.
If you have:
a = b
b = 'ty'
in first line you assign variable a to the same object as b. In the second line, you assign a new String object to variable b. So in the end both variables have references to different objects.

How do I access the elements in a hash which is itself a value in a hash?

I have this hash $chicken_parts, which consists of symbol/hash pairs (many more than shown here):
$chicken_parts = { :beak = > {"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}, :claws => {"name"=>"Claws", "color"=>"Dirty", function"=>"Scratching"} }
Then I have a class Embryo which has two class-specific hashes:
class Embryo
#parts_grown = Hash.new
#currently_developing = Hash.new
Over time, new pairs from $chicken_parts will be .merge!ed into #parts_grown. At various times, #currently developing will be declared equal to one of the symbol/hash pairs from #parts_grown.
I'm creating Embryo class functions and I want to be able to access the "name", "color", and "function" values in #currently_developing, but I don't seem to be able to do it.
def grow_part(part)
#parts_grown.merge!($chicken_parts[part])
end
def develop_part(part)
#currently_developing = #parts_grown[part]
seems to populate the hashes as expected, but
puts #currently_developing["name"]
does not work. Is this whole scheme a bad idea? Should I just make the Embryo hashes into arrays of symbols from $chicken_parts, and refer to it whenever needed? That seemed like cheating to me for some reason...
There's a little bit of confusion here. When you merge! in grow_part, you aren't adding a :beak => {etc...} pair to #parts_grown. Rather, you are merging the hash that is pointed too by the part name, and adding all of the fields of that hash directly to #parts_grown. So after one grow_part, #parts_grown might look like this:
{"name"=>"Beak", "color"=>"Yellowish orange", "function"=>"Pecking"}
I don't think that's what you want. Instead, try this for grow_part:
def grow_part(part)
#parts_grown[part] = $chicken_parts[part]
end
class Embryo
#parts_grown = {a: 1, b: 2}
def show
p #parts_grown
end
def self.show
p #parts_grown
end
end
embryo = Embryo.new
embryo.show
Embryo.show
--output:--
nil
{:a=>1, :b=>2}

Special syntax for declaring Ruby objects

Everyone knows two of the ways to create an empty array: Array.new and []. The first one is 'standard', you might say, and the second one is simply syntax sugar. Many different objects such as Hash and maybe even String are shorthanded through this method.
My question is: Is there a way to define my own delimimers for objects? An example would be <>. Maybe an alias like '<' => 'MyObject.new(' and '>' => ')'?
[] is an array literal, {} is a hash literal. There are plenty of these shorthand forms in Ruby. Check this wikibook out for more information.
There is no object literal, but you can use (source):
a = Struct.new(:foo,:bar).new(34,89)
a.foo # 34
a.bar # 89
No. (And ew anyway.) Delimiters are part of the parse process.
You can define operators, like <; that's different than a delimiter. For example, you could redefine < to take a block, and use that block to create a class, or a method, etc. But... I don't think I would.
You could do:
class MyObject; end
def [](*args)
MyObject.new *args
end
# but you can't use it directly:
o = [] #=> [] (empty Array)
# you must to refer to self:
o = self[] #=> #<MyObject:0x1234567>
# but since self depends on where are you, you must assign self to a global variable:
$s = self
o = $s[]
# or to a constant:
S = self
o = S[]
# however, in that case it's better to do it in the proper class:
class << MyObject
def [](*args)
new *args
end
end
# and assign it to a single-letter constant to reduce characters:
S = MyObject
# so
o = S[] #=> #<MyObject:0x1234568>
I can't think on something more compact.

Resources