Convert embedded blocks into embedded arrays with Ruby - ruby

Considering the following code:
class Node
def initialize(name=nil)
#name = name
#children = []
end
def node(name, &block)
child = Node.new(name)
#children.push(child)
child.instance_exec(&block) if block
end
end
def tree(name, &block)
#tree = Node.new(name)
#tree.instance_exec(&block)
#tree
end
t = tree("Simpsons family tree") do
node("gramps") do
node("homer+marge") do
node("bart")
node("lisa")
node("maggie")
end
end
end
puts "tree = " + t.inspect
Which is returning:
tree = #<Node:0x007fca1a103268 #name="Simpsons family tree", #children=[#<Node:0x007fca1a103128 #name="gramps", #children=[#<Node:0x007fca1a102fe8 #name="homer+marge", #children=[#<Node:0x007fca1a102ef8 #name="bart", #children=[]>, #<Node:0x007fca1a102e80 #name="lisa", #children=[]>, #<Node:0x007fca1a102e08 #name="maggie", #children=[]>]>]>]>
I would like to know if it was possible to make an update in order to return on-the-fly an embedded array of arrays, without using the #children shared array. I would expect this result:
[
"Simpsons family tree",
[
"gramps",
[
"homer+marge",
[
"bart",
"lisa",
"maggie"
]
]
]
]
Is it possible? Thanks for any suggestions.
Edit:
Actually, I would like to do so with basically the same code, but without any #children instance. So I want to remove #children.
Edit 2:
Here is my best result, after some tests:
class Node
def initialize(name=nil)
#name = name
end
def node(name, &block)
child = Node.new(name)
#sub = child.instance_exec(&block) if block
[
name,
#sub
].compact
end
end
def tree(name, &block)
#tree = Node.new(name)
[
name,
#tree.instance_exec(&block)
]
end
t = tree("Simpsons family tree") do
node("gramps") do
node("homer+marge") do
node("bart")
node("lisa")
node("maggie")
end
end
end
puts t.inspect
# => [
# "Simpsons family tree",
# [
# "gramps",
# [
# "homer+marge",
# [
# "maggie"
# ]
# ]
# ]
# ]
But there's still a trouble with the flatten nodes. Because only the last one is returned by Ruby.

The formatting of mine isn't exactly what you want, but that's not really the important part.
If you allow your nodes to be initialized with a collection of children, you can have your node method return a new node each time it is called.
class Node
def self.new(*args, &block)
instance = super
if block
instance.instance_eval(&block)
else
instance
end
end
def initialize(name, children=[])
#name = name
#children = children
end
attr_reader :children, :name
def node(name, &block)
new_node = Node.new(name)
new_node = new_node.instance_eval(&block) if block
Node.new(self.name, next_children + [new_node])
end
def next_children
children.map{|child| Node.new(child.name, child.next_children) }
end
def inspect
return %{"#{name}"} if children.empty?
%{"#{name}", #{children}}
end
end
t = Node.new("Simpsons family tree") do
node("gramps") do
node("homer+marge") do
node("bart").
node("lisa").
node("maggie")
end
end
end
puts t.inspect
#=> "Simpsons family tree", ["gramps", ["homer+marge", ["bart", "lisa", "maggie"]]]
By changing the behavior of initialization and of the node method you can accumulate the nodes as they are created.
I also took the liberty to remove your tree method since it was just a wrapper for initializing the nodes, but that might be a place to bring back formatting.
I came across this post looking for examples of good use of instance_exec for my Ruby DSL Handbook but you can either use instance_exec or instance_eval for the block since you're not passing any arguments to it.
EDIT: I updated the approach to return new values each time. The change required that nodes without blocks be chained together because each node call return a new object. The return value for the block is a new node.
In order to get the formatting you want, you'd need to do [t].inspect

Related

How do I write a Ruby DSL that can produce nested hashes?

I'm trying to write a Ruby DSL module that can be used to construct a hash with nested keys, with syntax like:
Tree.new do
node :building do
string :name, 'Museum'
node :address do
string :street, 'Wallaby Way'
string :country, 'USA'
end
end
end.to_h
The intended output of that invocation would be this hash:
{
"building": {
"name": "Museum",
"address": {
"street": "Wallaby Way",
"country": "USA"
}
}
}
I may need to support several levels of depth for the nodes, but can't quite work out how to model that.
I've got this so far, initializing the tree class with an empty hash, and evaluating the block passed to it:
class Tree
def initialize(&block)
#tree = {}
instance_eval &block
end
def to_h
#tree
end
def node(name, &block)
#tree[name] = block.call
end
def string(name, value)
#tree[name] = value
end
end
But it gives me this flat structure:
=> {:name=>"Museum", :street=>"Wallaby Way", :country=>"USA", :address=>"USA", :building=>"USA"}
I think I'm sort of on the right lines of trying to call the block within node, but can't quite work out how to have each node apply its contents to its part of the hash, rather than #tree[name], which is obviously that key on the top level hash?
You need to initialize a new tree when you call node.
class Tree
def initialize(&block)
#tree = {}
instance_eval &block
end
def to_h
#tree
end
def node(name, &block)
#tree[name] = (Tree.new &block).to_h
end
def string(name, value)
#tree[name] = value
end
end

assigning a popped class object from a hash table into a variable?

I have a 2D hash table of a class type that I made. I'm trying to pop a value out of that hash table and assign it to a variable:
foo = #bar[0].pop()
However, foo is not being populated with the object. I've made sure #bar[0].pop actually pops the right object as my original implementation is in a Class method with this code:
#test.each {|x| return #bar[x].pop() if something == x }
and the method returns the correct object. However, I'm trying to do more actions within the method which requires me to hold the popped object in a variable, and I'm not sure how to do that by assignment or if my syntax is just incorrect.
EDIT: Here's my actual code (stuff that's pertinent)
class Candy
attr_accessor :name, :shelved
def initialize
#name = ""
#shelved = 0
end
end
class Shelf
attr_accessor :candy_hash, :total_candies, :shelf_full, :num_candies,
:name_array
def initialize()
#candy_hash = Hash.new{ |h, k| h[k] = []}
#total_candies = 0
#shelf_full = 0
#name_array = Array.new
end
end
class Shop
attr_accessor :shelves, :unshelved_hash, :unshelved_names
def initialize
#shelves = []
#unshelved_hash = Hash.new{ |h, k| h[k] = []}
#unshelved_names = []
end
def push_new_unshelved(candy)
#unshelved_names << candy.name
#unshelved_hash[#unshelved_names.last].push(candy)
end
def push_existing_unshelved(candy)
#unshelved_names.each{|x| #unshelved_hash[x].push(candy) && break if x == candy.name}
end
def receive_candy(candy)
check = self.check_if_exists(candy)
if check == 0
self.push_new_unshelved(candy)
else
self.push_existing_unshelved(candy)
end
end
def get_hash_key(candy_name)
#unshelved_names.each{|x| return x if candy_name == x}
end
def get_unshelved_candy(candy_name)
candy = #unshelved_hash[self.get_hash_key(candy_name)].pop()
return candy
# problem is here, candy is never populated
# I'm gonna do more in this method but this is the important part
# working solution right now: return #unshelved_hash[self.get_hash_key(candy_name)].pop()
end
def shelve_single(candy_name)
candy = self.get_unshelved_candy(candy_name)
#shelves[self.find_empty_shelf].add_candy(candy)
end
end
shop1.receive_candy(Candy.new("snickers"))
shop1.shelve_single("snickers")
foo is not being populated with the object.
Assignments to local variables cannot silently fail. foo = #bar[0].pop() will assign the result from #bar[0].pop() to foo. (or raise an exception if #bar[0].pop() fails)
If by "not being populated" you mean a value of nil then it's because #bar[0].pop() returned nil. Most likely this is because #bar[0] is an empty array. (maybe because it wasn't populated in the first place)
Here's my actual code (stuff that's pertinent)
At first glance, your code seems to be unnecessarily complex. When working with defaults-based hashes like #candy_hash = Hash.new { |h, k| h[k] = [] } you can access the underlying arrays simply via their key, e.g. #candy_hash["snickers"]. And since the hash automatically creates an empty array as needed, there's no need to check whether it exists. You can just push elements:
candies = Hash.new { |h, k| h[k] = [] }
candies["snickers"].push(Candy.new("snickers"))
candies["snickers"].push(Candy.new("snickers"))
candies["kit kat"].push(Candy.new("kit kat"))
candies
#=> {
# "snickers"=>[
# #<Candy:0x00007fad03046818 #name="snickers">,
# #<Candy:0x00007fad030467c8 #name="snickers">
# ],
# "kit kat"=>[
# #<Candy:0x00007f981303a658 #name="kit kat">
# ]
# }
candies.pop("snickers")
#=> #<Candy:0x00007fad030467c8 #name="snickers">
And to get the candy names, you could simply inspect the hash's keys: (instead of maintaining a separate array)
candies.keys
#=> ["snickers", "kit kat"]
Regarding your problem, let's start by using an incomplete but working test implementation for Candy and Shelf:
class Candy
attr_reader :name
def initialize(name)
#name = name
end
end
class Shelf
attr_reader :candy_hash
def initialize
#candy_hash = Hash.new { |h, k| h[k] = [] }
end
def add_candy(candy)
candy_hash[candy.name].push(candy)
end
def remove_candy(candy_name)
candy_hash[candy_name].pop
end
end
Based on this, we can implement Shop as follows:
class Shop
attr_reader :shelves, :unshelved_hash
def initialize
#shelves = []
#unshelved_hash = Hash.new { |h, k| h[k] = [] }
end
def receive_candy(candy)
#unshelved_hash[candy.name].push(candy)
end
def get_unshelved_candy(candy_name)
#unshelved_hash[candy_name].pop
end
def find_empty_shelf
#shelves.first # replace with actual logic
end
def shelve_single(candy_name)
candy = get_unshelved_candy(candy_name)
empty_shelf = find_empty_shelf
empty_shelf.add_candy(candy)
end
end
Example: (I've omitted the object ids)
shop = Shop.new
shop.shelves << Shelf.new
shop.receive_candy(Candy.new("snickers"))
shop.unshelved_hash #=> {"snickers"=>[#<Candy #name="snickers">]}
shop.shelves #=> [#<Shelf #candy_hash={}>]
shop.shelve_single("snickers")
shop.unshelved_hash #=> {"snickers"=>[]}
shop.shelves #=> [#<Shelf #candy_hash={"snickers"=>[#<Candy #name="snickers">]}>]
You could also replace unshelved_hash by an instance of Shelf in order to reuse its code:
class Shop
attr_reader :shelves, :unshelved
def initialize
#shelves = []
#unshelved = Shelf.new
end
def receive_candy(candy)
#unshelved.add_candy(candy)
end
def get_unshelved_candy(candy_name)
#unshelved.remove_candy(candy_name)
end
# ...
end
The Shop implementation(s) above aren't complete but they should get you started.
A common pitfall with hashes that assign values lazily is that they don't handle deletion automatically. You might have noticed that after shelving our test Snickers, the unshelved_hash still contains "snickers"=>[]. To remove it completely, you can delete the corresponding key after pop-ing off the array's last item:
def remove_candy(candy_name)
candy = candy_hash[candy_name].pop
candy_hash.delete(candy_name) if candy_hash[candy_name].empty?
candy
end
Note: This answer is based on quite a few assumptions to fill-up the gaps in your question.
You need to use break instead of return if you want to assign the value to a variable within a method. return exits the whole method while break only interrupts the current iterator (each in your case):
def my_method
my_value = #test.each {|x| break #bar[x].pop() if something == x }
...
end

How do I randomly select a name from an array and check if it's uppercase

I need to randomly pick a name from an array in Ruby and then check if it uppercase. So far I have:
def namegenerator
return #name.sample
end
def namechecker
if name.upcase then
check = TRUE
else
check = FALSE
end
end
It needs to be as two separate methods like this.
Something like this:
def sample_word(words)
words.sample
end
def upcase?(word)
word == word.upcase
end
And then something like:
words = %w[APPLE banana CherRy GRAPE]
word = sample_word(words)
puts word # e.g. BANANA
puts upcase?(word) # will print true
If you just want to check just the first letter:
names = %w(Kirk mccoy scott Spock)
names.sample.then { |name| [name, name[0] == name[0].upcase] }
#=> ["mccoy", false]
Maybe something like this:
class NameGenerator
def initialize(size, items)
#name = ""
#size = size
#items = items
end
def namegenerator
#name = #items.sample(#size).to_s
end
def namechecker?
#name == #name.upcase
end
def name
#name
end
end
ng = NameGenerator.new 1, ["name", "Name", "NAME"]
ng.namegenerator
puts ng.name, ng.namechecker?
Update
I've posted code without much thinking about abstraction and i think it would be much better to encapsulate name and upper case check to separate class and make it immutable, then make generator class that selects one entity from collection.
class NameGenerator
def initialize(items)
#items = items
end
def next
#items.sample
end
end
class Name
attr_reader :name
def initialize(name)
#name = name
end
def is_uppercase?
#name.match(/\p{Lower}/) == nil
end
end
ng = NameGenerator.new [
Name.new("name"),
Name.new("Name"),
Name.new("NAME"),
Name.new("na-me")
]
name = ng.next
puts name.name, name.is_uppercase?

How can I use ruby flat_map to map to tuples of children AND parent

Imagine you have an array of objects called parents and each entry is an instance of a Parent. And imagine each parent has a method called children which returns an array of Child instances. Now imagine you want to flat_map from the parents to the children but in a later step you will still need access to the parent (for, say, filtering or whatever). How would you do it?
parents
.flat_map { |parent| parent.children.map { |child| {parent: parent, child: child} } }
Would give me what I need, but... eww. Surely there's a more ruby-esque way of doing this? It almost seems like an RX merge or combineLatest. Can't figure out a ruby way of doing this other than what I already have.
Here's a bit of ruby to generate the parent/child structure I'm talking about using random data:
class Child ; end
class Parent
attr_reader :children
def initialize(children)
#children = children || []
end
end
parents = 100.times.map do
Parent.new(rand(10).times.map { Child.new })
end
Consider adopting the following structure.
class Child
attr_accessor :name # create accessor for name of child
def initialize(name)
#name = name
end
end
class Parent
#families = {} # create class instance variable
class << self # change self to singleton class
attr_reader :families # create accessor for #families
end
def initialize(name, children)
self.class.families[name] = children.map { |name| Child.new(name) }
end
end
Parent.new("Bob and Diane", %w| Hector Lois Rudolph |)
#=> #<Parent:0x00005ac0ddad9aa0>
Parent.new("Hank and Trixie", %w| Phoebe |)
#=> #<Parent:0x00005ac0ddb252c0>
Parent.new("Thelma and Louise", %w| Zaphod Sue |)
#=> #<Parent:0x00005ac0ddcb2890>
Parent.families
#=> {"Bob and Diane" =>[#<Child:0x00005ac0ddadf9f0 #name="Hector">,
# #<Child:0x00005ac0ddadf388 #name="Lois">,
# #<Child:0x00005ac0ddadf1a8 #name="Rudolph">],
# "Hank and Trixie" =>[#<Child:0x00005ac0ddb251d0 #name="Phoebe">],
# "Thelma and Louise"=>[#<Child:0x00005ac0ddcb27c8 #name="Zaphod">,
# #<Child:0x00005ac0ddcb27a0 #name="Sue">]}
Parent.families.keys
#=> ["Bob and Diane", "Hank and Trixie", "Thelma and Louise"]
Parent.families["Bob and Diane"].map { |child| child.name }
#=> ["Hector", "Lois", "Rudolph"]

Is there a way to pass a method to a method of the same object?

Here's what I'd like to do:
class Directory
def doSomething
end
def subs
# => an array of Directory objects
end
def recursively (method)
self.subs.each do |sub|
sub.method
sub.recursively method
end
end
end
cd = Directory.new
cd.recursively 'doSomething'
# ...and extra points if theres a way to:
cd.recursively.doSomething
To put this into perspective I'm creating a small script that will make changes to files in a Directory as well as all its sub-directories. These sub-directories will just be extended Directory objects.
So is there a way to pass a method as a parameter of another method?
You could use Object#send, where method is a string or symbol representing the method name, as in your first example. Just change your #recursively to this:
def recursively(method)
subs.each do |sub|
sub.send method
sub.recursively method
end
end
UPDATE
For your "extra points" question, and picking up on megas' answer, here's a stab at an Enumerator-based approach. Drop this into your Directory:
def recursively
Enumerator.new do |y|
subs.each do |sub|
y.yield sub
sub.recursively.each do |e|
y.yield e
end
end
end
end
And call it like this:
cd.recursively.each { |d| d.doSomething }
Yes, you can do this -
class Directory
def doSomething
end
def subs
# => an array of Directory objects
end
def recursively (method)
self.subs.each do |sub|
sub.method.call
sub.recursively method
end
end
end
dir = Directory.new
ds = dir.method :doSomething
dir.recursively ds
I think here's should be specialized each method from Enumerable module. When you implement the each method, then Enumerable module will give a lot of handy methods like map, drop and so on.
class Directory
include Enumerable
def initialize
# here you should build #objects - a whole list of all objects in
# the current direcory and its subdirectories.
#objects = ....
end
def each
if block_given?
#objects.each { |e| yield(e) }
else
Enumerator.new(self, :each)
end
end
...
end
And then you can iterate all objects in elegant way:
#directory = Directory.new('start_directory')
#directory.each do |object|
puts object.size # this will prints the sizes for all objects in directory
object.do_some_job # this will call method on object for all your objects
end
This one will give an array of sizes for all objects in directory
#directory.map { |object| object.size } #=> [435435,64545,23434,45645, ...]
Aditional example:
For example you need to get the list with indexes and sizes of all objects
#directory.each_with_index.map { |object, index| [index, object.size] }
#=> [ [0,43543], [1,33534], [2,34543564], [3,345435], ...]
See if this gets you headed in the right direction:
module Recursion
def recursively(&block)
output = block.call(self)
output.recursively(&block) unless output.nil?
end
end
class Number
include Recursion
def initialize(value)
#value = value
end
def next
#value > 0 ? Number.new(#value - 1) : nil
end
def print
puts #value
self.next
end
end
Number.new(10).recursively(&:print)

Resources