I want to using ruby to create class inherirance hierarchy. I have grep the class name and its parent class name like this:
[[B,A], [C,A], [E,D], [F,B] ...]
[B,A] A is B's parent class, and the root class not only one, like A or D.
The elements in the array is just string, like A,B,C...
I want to create a inherirance hierarchy graph like this:
[
A=>[
B=>[F],
C
],
D=>[E]
]
the graph format is not strict, which can demonstrate the hierarchy will be OK.
I have try to using loop to recursively placed the node, but it's too low efficiency. Does anyone can help me or there is some gem to solve this?
All I want to know is the clas inherirance hierarchy, so whatever the way you solve this.
Thanks #Max and #Gabriel de Oliveira for answer my question! I have solve the problem.
It maybe ugly, but it works.
class Node
attr_accessor :name, :parent, :children
def initialize(name)
#name = name
#parent = nil
#children = Set.new
end
def <=>(other)
if other.name == self.name
return 0
end
return nil
end
def ==(other)
if other.name == self.name
return true
end
return false
end
def inspect
desc = ""
desc << "{" if #children.length > 0
desc << %("#{#name}")
if #children.count > 0
desc << ":["
children_arr = #children.to_a
children_arr.to_a.each_index do |index|
desc << ',' if index > 0
desc << children_arr[index].inspect
end
desc << "]"
end
desc << "}" if #children.length > 0
return desc
end
end
str = string_from_file(file_path)
arr = JSON.parse(str)
nodes = {}
# create nodes set
arr.each do |item|
name = item[0]
parent = item[1]
nodes[name] = Node.new(name)
nodes[parent] = Node.new(parent)
end
# bind relationship with nodes
arr.each do |item|
node = nodes[item[0]]
parent = nodes[item[1]]
if !parent.nil?
node.parent = parent
parent.children << node
end
end
# filter the root nodes
roots = []
nodes.each_value do |node|
roots << node if node.parent.nil?
end
puts roots
You can do this with metaprogramming. B = Class.new(A) creates a class B that inherits from A and Object.const_set and Object.const_get can dynamically access constants. The only tricky bit is creating the classes in the right order, but that can be solved with recursion.
# create class klass (and any necessary superclasses) with inheritance specified by tree
def create_class_from_tree(klass, tree)
# nothing to do if the class already exists
return if Object.const_defined? klass
# default superclass
parent = Object
# look for a superclass defined in the tree
if inherit = tree.find { |k, p| k == klass }
# ensure superclass exists
parent = create_class_from_tree(inherit[1], tree)
end
Object.const_set(klass, Class.new(parent))
end
# create all classes with inheritance specified by tree
def create_classes_from_tree(tree)
tree.each { |klass, _| create_class_from_tree(klass, tree) }
end
create_classes_from_tree([['B', 'A'], ['C', 'A'], ['E', 'D'], ['F', 'B']])
('A'..'F').each { |klass| puts "#{klass} < #{Object.const_get(klass).superclass}" }
# A < Object
# B < A
# C < A
# D < Object
# E < D
# F < B
To transform the input array into a nested hash you could implement something along these lines:
def process(input)
return {} if input.empty?
base_classes, rest = input.partition {|value| value.is_a? Class }
ret_val = {}
base_classes.each do |klass|
ret_val[klass], rest = rest.partition {|value| value.last == klass }
ret_val[klass] = ret_val[klass].map(&:first)
end
ret_val.each {|klass, classes| ret_val[klass] = process(classes + rest) }
end
Then given all clases are defined and your data array is sanitized (for example it doesn't contain an invalid element such as [X, Z]):
data = [A, [B, A], [C, A], D, [E, D], [F, B]]
process(data) #=> {A=>{B=>{F=>{}}, C=>{}}, D=>{E=>{}}}
Related
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
I have the following class:
class AEnumerableObject
include Enumerable
def initialize
#original_list = []
end
def [] index
#original_list[index]
end
def []= index, value
#original_list[index] = value
end
def each &block
#original_list.each_with_index do |item, i|
yield item, i
end if block_given?
end
end
If I run inline commands
$ object = AEnumerableObject.new
$ object[0] = 1
$ object[1] = 2
$ object[2] = 3
$ p object
should show
[1, 2, 3]
but it actually shows
#<AEnumerableObject:... #original_list=[1, 2, 3]>
while if I run
$ p object.class
it shows
=> AEnumerableObject
How do I implement an Array like print method?
UPDATED
I implemented the methods #[], #[]= and #each (so now it’s iterable). But how to show it like an Array in line command?
Your problem is that you have not implemented the inspect method. At the moment, the default inspect definition is used in your class. You may want to define it like:
class AEnumerableObject
def inspect
#original_list.inspect
end
end
As you can see on the left column (with all the defined methods in the module) of the documentation, the Enumerate module does not define the methods #[] and #[]=, which must be provided by the class.
Also, the class that includes Enumerate must provide an #each method.
class Test
include Enumerate
def [](i)
# do your stuff
end
def []=(i, v)
# do your stuff
end
def each
# required!
end
end
Implementing the required methods does not create any (let's say) "relation" with the Array class. The only way to create such a "relation" is through inheritance.
Let's make a silly example with some sort of linked list just to see how it works the idea...It is not robust nor well implemented... It is only an idea:
class Node
include Enumerable
attr_reader :data
def initialize(payload, n = nil)
#node = n if n.is_a? Node
#data = payload
end
def [](n)
if n == 0
self
else
#node[n - 1]
end
end
def []=(n, v)
if n == 0
#data = v
else
#node[n - 1] = v
end
end
def each(&block)
block.call(self)
#node.each(&block) if #node
end
def <=>(o)
#data <=> o.data
end
def to_s
"[" + self.map { |e| "#{e.data}" }.join(", ") + "]"
end
end
a = Node.new("Node 0")
b = Node.new("Node 1", a)
c = Node.new("Node 2", b)
d = Node.new("Node 3", c)
d[2] = "Edited"
d.each do |n|
puts n.data
end
# Let's use an enumerable method
s = d.select { |n| n.data == "Node 0" }[0]
puts "#{a.inspect} = #{s.inspect}"
puts a, b, c, d
# => Output:
#
# Node 3
# Node 2
# Edited
# Node 0
# #<Node:0x0000562e121e2440 #data="Node 0"> = #<Node:0x0000562e121e2440 #data="Node 0">
# [Node 0]
# [Edited, Node 0]
# [Node 2, Edited, Node 0]
# [Node 3, Node 2, Edited, Node 0]
As alternatives, you may consider:
# 1. Inheritance
def Test < Array
# do your stuff
end
# 2. Reopen the Array class
def Array
# do your stuff
end
The methods #[] and #[]= for the Array class are defined in the C source of the interpreter.
When I call first_array | second_array on two arrays that contain custom objects:
first_array = [co1, co2, co3]
second_array =[co2, co3, co4]
it returns [co1, co2, co3, co2, co3, co4]. It doesn't remove the duplicates. I tried to call uniq on the result, but it didn't work either. What should I do?
Update:
This is the custom object:
class Task
attr_accessor :status, :description, :priority, :tags
def initiate_task task_line
#status = task_line.split("|")[0]
#description = task_line.split("|")[1]
#priority = task_line.split("|")[2]
#tags = task_line.split("|")[3].split(",")
return self
end
def <=>(another_task)
stat_comp = (#status == another_task.status)
desc_comp = (#description == another_task.description)
prio_comp = (#priority == another_task.priority)
tags_comp = (#tags == another_task.tags)
if(stat_comp&desc_comp&prio_comp&tags_comp) then return 0 end
end
end
and when I create few instances of Task type and drop them into two different arrays and when I try to call '|' on them nothing happens it just returns array including both first and second array's elements without the duplicates removed.
No programming language for itself can be aware if two objects are different if you don't implement the correct equality methods.
In the case of ruby you need to implement eql? and hash in your class definition, as these are the methods that the Array class uses to check for equality as stated on Ruby's Array docs:
def eql?(other_obj)
# Your comparing code goes here
end
def hash
#Generates an unique integer based on instance variables
end
For example:
class A
attr_accessor :name
def initialize(name)
#name = name
end
def eql?(other)
#name.eql?(other.name)
end
def hash
#name.hash
end
end
a = A.new('Peter')
b = A.new('Peter')
arr = [a,b]
puts arr.uniq
Removes b from Array leaving only one object
Hope this helps!
The uniq method can take a block that defines what to compare the objects on. For example:
class Task
attr_accessor :n
def initialize(n)
#n = n
end
end
t1 = Task.new(1)
t2 = Task.new(2)
t3 = Task.new(2)
a = [t1, t2, t3]
a.uniq
#=> [t1, t2, t3] # because all 3 objects are unique
a.uniq { |t| t.n }
#=> [t1, t2] # as it's comparing on the value of n in the object
I tried the solution from fsaravia above and it didn't work out for me. I tried in both Ruby 2.3.1 and Ruby 2.4.0.
The solution I've found is very similar to what fsaravia posted though, with a small tweak. So here it is:
class A
attr_accessor :name
def initialize(name)
#name = name
end
def eql?(other)
hash.eql?(other.hash)
end
def hash
name.hash
end
end
a = A.new('Peter')
b = A.new('Peter')
arr = [a,b]
puts arr.uniq
Please, don't mind that I've removed the # in my example. It won't affect the solution per se. It's just that, IMO, there wasn't any reason to access the instance variable directly, given a reader method was set for that reason.
So...what I really changed is found inside the eql? method, where I used hash instead name. That's it!
If you look at the Array#| operator it says that it uses the eql?-method, which on Object is the same as the == method. You can define that by mixin in the Comparable-module, and then implement the <=>-method, then you'll get lots of comparison-methods for free.
The <=> operator is very easy to implement:
def <=>(obj)
return -1 if this < obj
return 0 if this == obj
return 1 if this > obj
end
Regarding your 'update', is this what you are doing:
a = Task.new # => #<Task:0x007f8d988f1b78>
b = Task.new # => #<Task:0x007f8d992ea300>
c = [a,b] # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>]
a = Task.new # => #<Task:0x007f8d992d3e48>
d = [a] # => [#<Task:0x007f8d992d3e48>]
e = c|d # => [#<Task:0x007f8d988f1b78>, #<Task:0x007f8d992ea300>, \
#<Task:0x007f8d992d3e48>]
and then suggesting that e = [a, b, a]? If so, that's the problem, because a no longer points to #<Task:0x007f8d988f1b78>. All you can say is e => [#<Task:0x007f8d988f1b78>, b, a]
I took the liberty to rewrite your class and add the methods that needs to be overwritten in order to use uniq (hash and eql?).
class Task
METHODS = [:status, :description, :priority, :tags]
attr_accessor *METHODS
def initialize task_line
#status, #description, #priority, #tags = *task_line.split("|")
#tags = #tags.split(",")
end
def eql? another_task
METHODS.all?{|m| self.send(m)==another_task.send(m)}
end
alias_method :==, :eql? #Strictly not needed for array.uniq
def hash
[#status, #description, #priority, #tags].hash
end
end
x = [Task.new('1|2|3|4'), Task.new('1|2|3|4')]
p x.size #=> 2
p x.uniq.size #=> 1
I created a very simple node class with a name and an array of nodes. I also created an iterator class with a next method that helps me iterate on each node and child nodes. I need to write the next method, but I don't what is the best way to do it.
class Node
def initialize(name, nodes
#name = name
#nodes = nodes
end
end
class Iterator
def initialize(node)
#node = node
end
def next
???
end
end
Example:
z = Node.new("z", [])
b = Node.new("b", [z])
c = Node.new("c", [])
parent = Node.new("a", [b, c])
iterator = Iterator.new(parent)
str = ''
next = iterator.next
while next do
str += next.name
next = iterator.next
end
str should equal "abzc"
Can anybody help me with this?
If I may suggest a more idiomatic approach:
class Node
attr_accessor :name, :children
def initialize(name, children = [ ])
#name = name
#children = children
end
def traverse(&block)
yield self
#children.each { |child| child.traverse(&block) }
end
end
z = Node.new("z")
b = Node.new("b", [z])
c = Node.new("c")
parent = Node.new("a", [b, c])
str = ''
parent.traverse { |node| str += node.name }
puts str
This has a benefit over btilly's solution (which is also correct) in that it doesn't proliferate Iterator objects and suck up memory- in fact Iterator disappears from the implementation (while still retaining the ability to DO something to each node in succession). This is more idiomatic; more Ruby-esque.
In your iterator, if node has any children, next would be the first of them. If it doesn't, then you need to "back up" to the last sibling that you skipped over. This implies that you need to keep track of the siblings that have been skipped over, so that you can go back to them.
Here is some running code that demonstrates what I think you're looking for.
class Node
attr_accessor :name, :nodes
def initialize(name, nodes)
#name = name
#nodes = nodes
end
end
class Iterator
def initialize(node)
#node = node
end
def each_node
yield #node
for node in #node.nodes do
iterator = Iterator.new(node)
iterator.each_node {|next_node|
yield next_node
}
end
end
end
z = Node.new("z", [])
b = Node.new("b", [z])
c = Node.new("c", [])
parent = Node.new("a", [b, c])
iterator = Iterator.new(parent)
str = ''
iterator.each_node {|node|
str += node.name
}
puts str
I have been able to solve my problem by doing the following. But what I don't like with this approach is that I traverse the nodes during the initialization instead of in the next method...
class Iterator
def initialize(node)
#node = node
#index = -1
#list = []
traverse(#node)
end
def next
#index += 1
#list[#index]
end
private
def traverse(root)
#list[#list.size] = root
if root.nodes
for n in root.nodes do
traverse(n)
end
end
end
end
In the following code, the issue is that after calling method .find_name on an object type of LogsCollection, the returned object becomes a native array and does not remain type LogsCollection. I believe the correct approach might be to create a constructor/initializer that accepts an array and return a brand new object of the correct type. But I am not sure there is not a better way to accomplish this?
Can a Ruby-pro eyeball this code and suggest (at the code level) the best way to make the returned object from .find_name remain type LogsCollection (not array)?
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection < Array
def names
collect do |i|
i.name
end
end
def find_name(name)
#name = name
self.select { |l| l.name == #name }
end
end
logs = LogsCollection.new
logs.push(Log.new('Smith', 1, false, 323.95, nil))
logs.push(Log.new('Jones', 1, false, 1000, nil))
logs = logs.find_name('Smith')
puts logs.count
unless logs.empty?
puts logs.first.name # works since this is a standard function in native array
puts logs.names # TODO: figure out why this fails (we lost custom class methods--LogsCollection def find_name returns _native_ array, not type LogsCollection)
end
Final code post-answer for anyone searching (note the removal of base class < array):
class Log
attr_accessor :name, :expense_id, :is_excluded, :amount, :paid_to
def initialize(name, expense_id, is_excluded, amount, paid_to)
#name = name
#expense_id = expense_id
#is_excluded = is_excluded
#amount = amount
#paid_to = paid_to
end
end
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def add(log)
#logs.push(log)
end
def names
#logs.collect { |l| l.name }
end
def find_name(name)
LogsCollection.new(#logs.select { |l| l.name == name })
end
end
logs = LogsCollection.new([])
logs.add(Log.new('Smith', 1, false, 323.95, nil))
logs.add(Log.new('Jones', 1, false, 1000, nil))
puts logs.names
puts '--- post .find_name ---'
puts logs.find_name('Smith').names
As you can see in the docs Enumerable#select with a block always returns an array. E.g.
{:a => 1, :b => 2, :c => 3}.select { |k,v | v > 1 }
=> [[:b, 2], [:c, 3]]
What you could do is have some sort of constructor for LogsCollection that wraps up a normal array as a LogsCollection object and call that in find_name.
As requested here's an example class (I'm at work and writing this while waiting for something to finish, it's completely untested):
class LogsCollection
attr_reader :logs
def initialize(logs)
#logs = logs
end
def names
#logs.collect { |i| i.name }
end
def find_name(n)
name = n
LogsCollection.new(#logs.select { |l| l.name == n })
end
# if we don't know a method, forward it to the #logs array
def method_missing(m, *args, &block)
#logs.send(m, args, block)
end
end
Use like
lc = LogsCollection.new
logs = lc.logs.find_name('Smith')