I don’t like to top post even a little bit, but I want to apologize for
the long post (don’t really like attachements), and I’m sorry if the way
I use Ruby makes some people cringe, but Ruby really makes my job easier
for everything from testing hardware units to comparing and sorting
bills of material, etc. Some people may want to skip to the end where
there are some examples.
Robert K. wrote:
On Fri, Oct 8, 2010 at 6:06 AM, John S. [email protected]
wrote:
So you have something like
class Foo
def self.each
yield “whatever”
self
end
end
? Did you also do
class Foo
def self.each
yield “whatever”
end
extend Enumerable
end
? That would give you all methods like #find_all etc. for free.
Robert,
Note that I have kept the code and comments to a minimum here as I did
not want the post to be even longer. I know that you are quite
knowledgeable and that you should have little trouble following what I
am doing - although you might not necessarily understand why I chose to
do it this way:
class Base
@class_all_enum_objects = Array.new
class << self
attr_accessor :class_all_enum_objects
end
def initialize
self.class.class_all_enum_objects << self
end
def self.enum
EnumeratorEOC.new(self, :each)
end
def self.each(&code_block)
self.class_all_enum_objects.each do |object|
yield object
end
end
end
module EnumeratorModifierEOC
def find_all(*condition_strings, &code_block)
if block_given?
find_array = super
else
find_array = # process condition strings (syntactic sugar) here.
end
ArrayEOC.new(find_array)
end
code for other methods such as sort, min, max, all?, etc. here.
end
class EnumeratorEOC < Enumerator # avoids modifying Enumerator.
include EnumeratorModifierEOC
def initilize(object, method_sym)
super
end
end
class ArrayEOC < Array # avoids modifying Array.
include EnumeratorModifierEOC
def initialize(array = [])
super(array)
end
end
To suit my purposes: 1) I want the class itself to keep track of all of
the class instances, as shown above, 2) if an attribute has more than
one value or is more complex than an Array, I prefer to define a class
for it (inherited from Base) and assign attributes, 3) I want syntactic
sugar for enumerables, 4) especially for sort, including default sort
parameters (min and max get defaults for free as a result of the
necessary <=> definition). 5) I want to be able to define new functions
and to be able to use syntactic sugar with them. See below. 6) I also
keep track of descendants of classes although this is only partly shown
below. There is a little more on this at the end if anyone is still
reading.
and I have added syntactic sugar to some of the methods. So I can be
lazy (and quick) and write things like:
Slot.enum.find_all(‘:slot_num >= 8’, ‘:slot_num <= 22’, “:rate ==
‘ufec’”).all?(‘:errors == 0’)
Is this equivalent to
enum.find_all {|s| (8…22) === s.slot_num && s.rate == ‘ufec’}.all?
{|s| e.errors == 0}
Yes, you are correct (e.errors is a typo - should be s.errors), they are
equivalent.
? If yes, I do not really see the advantage of your approach. It’s
slower needs a similar amount of typing and will detect errors later
(at execution time vs. at parse time).
If my assumption above is correct I do not understand where the “easy
way out” is.
I think ‘sort’ is the main reason for my ‘easy way out’. Plus the use of
additional functions. To illustrate, I have included in the code below
one such function, ‘sum_total_eoc’ which is just ‘inject’ set up for
addition, but with the ability to use syntactic sugar (of course).
You may or may not agree with my ‘easy way out’, but here goes:
First, a short explanation: I have attribute and expression comparables
for each class. The comparables are compared in the order that they are
given in attr_accessor. There is also a class method called
set_comparables_order that allows the user to re-arrange the order if
desired (it also allows expressions, not just attributes). Child classes
inherit the comparables and their order from the parent class. Any new
attributes defined in attr_accessor in the child class are added to the
comparables up front.
To do this, the following is added to the code above (stripped of error
checking, etc. to avoid clutter):
class Base
@class_comparables = Array.new
class << self
attr_accessor :class_comparables
end
self.inherited(child_class)
child_class.class_all_enum_objects = Array.new # init for the new
class.
child_class.class_comparables = self.class_comparables # inherit.
end
def self.attr_accessor(*accessor_names)
self.class_comparables.replace(accessor_names +
self.class_comparables).uniq
super
# also update descendants’ class_comparables if needed.
end
def self.set_comparables_order(*attr_and_expr)
self.class_comparables.replace(attr_and_expr +
self.class_comparables).uniq
also update descendants’ class_comparables if needed.
end
def self.enum
EnumeratorEOC.new(self, :each, class_comparables) # added
class_comparables
end
def <=>(other)
# use the comparables list here.
end
end
class EnumeratorEOC
def initilize(object, method_sym, comparables)
@comparables = comparables
super(object, method_sym)
end
end
class ArrayEOC
def initialize(array = [], comparables)
@comparables = comparables
super(array)
end
def sum_total_eoc(*params_exprs) # added this function.
self.inject(0) do |sum, object|
# process params_exprs syntactic sugar on object here.
end
end
end
module EnumeratorModifierEOC
def sort(*params_exprs, &code_block) # sho w sort, since it is of
interest.
if block_given?
return_array = super
else
return_array = # process params_exprs including setting
comparables.
end
ArrayEOC.new(return_array, comparables) # comparables propagate.
end
end
OK, so what can I do with this? The following are some simplified
examples:
class Slot < EnumerableObjectClass::Base
attr_accessor :slot_num, :rate, :error_info # <–Array of ErrorInfo
objects.
def initialize(slot_num, rate, error_info)
super()
@slot_num, @rate, @error_info = slot_num, rate, error_info
end
end
class ErrorInfo < EnumerableObjectClass::Base
attr_accessor :time_stamp, :num_errors
def initialize(time_stamp, num_errors)
super()
@time_stamp, @num_errors = time_stamp, num_errors
end
end
Slot.new(3, ‘efec’, [ErrorInfo.new(‘10-07-10’, 500),
ErrorInfo.new(‘10-08-10’, 1000)])
Slot.new(1, ‘ufec’, [ErrorInfo.new(‘10-04-10’, 1000),
ErrorInfo.new(‘10-05-10’, 2000)])
Slot.new(8, ‘ufec’, [ErrorInfo.new(‘10-07-10’, 1500),
ErrorInfo.new(‘10-08-10’, 1200)])
Slot.new(4, ‘efec’, [ErrorInfo.new(‘10-07-10’, 3500)])
Slot.enum{}.find(‘:slot_num == 4’).error_info <<
ErrorInfo.new(‘10-08-10’, 2500)
note - most of the examples below do not use variables. However, this
is not a limitation and a couple of examples are given that use
variables.
default sort is sorted by :slot_num, etc. - order of attr_accessor
p Slot.enum.sort.collect(:slot_num)
=> [[1], [3], [4], [8]]
sort by total errors.
p
Slot.enum.sort(‘:error_info.sum_total_eoc(:num_errors)’).collect(:rate,
‘:error_info.sum_total_eoc(:num_errors)’)
=> [[“efec”, 1500], [“ufec”, 2700], [“ufec”, 3000], [“efec”, 6000]]
sort by rate, total errors.
p Slot.enum.sort(:rate,
‘:error_info.sum_total_eoc(:num_errors)’).collect(:rate,
‘:error_info.sum_total_eoc(:num_errors)’)
=> [[“efec”, 1500], [“efec”, 6000], [“ufec”, 2700], [“ufec”, 3000]]
sort by rate, total errors, with total errors sorted high to low by
ending with ‘’ triple underscore.
p Slot.enum.sort(:rate,
':error_info.sum_total_eoc(:num_errors)’).collect(:rate,
‘:error_info.sum_total_eoc(:num_errors)’, :slot_num)
=> [[“efec”, 6000, 4], [“efec”, 1500, 3], [“ufec”, 3000, 1], [“ufec”,
2700,8]]
I think that the above would be hard to do in a one-liner w/o syntactic
sugar, so this is my ‘easy way out’ (although I would not be surprised
if you came up with one Robert). I think this is pretty easy and it
saves me time.
total errors of all slots.
p Slot.enum.sum_total_eoc(‘:error_info.sum_total_eoc(:num_errors)’)
=> 13200
slots that have >= 3000 total errors.
p Slot.enum.find_all(‘:error_info.sum_total_eoc(:num_errors) >=
3000’).collect(:slot_num)
=> [[1], [4]]
use a variable for sort parameter.
var = ‘:slot_num’
p Slot.enum.sort(“#{var}”).collect(“#{var}”)
=> [[1], [3], [4], [8]]
use a variable w/o interpolation - must pass a binding with enum{}
(which is where this topic started by the way). It is carried along as
an attribute to ArrayEOC and/or EnumeratorEOC, similar to coparables.
var = ‘:slot_num’
p Slot.enum{}.sort(var).collect(var)
=> [[1], [3], [4], [8]]
slots that have >= 2500 errors in a single time period.
p Slot.enum.find_all(“:error_info.any?(:num_errors >=
2500)”).collect(:slot_num)
=> Exceptions galore! my simple ‘parser’ breaks down. will have to use
the ‘old fashioned way’ of a code block. maybe I will try to get this to
work in the future.
In case anyone is wondering, if data is input as an Array, it comes out
as an ArrayEOC the first time it is read. In the simplified code above,
I show a call to super in attr_accessor, but really for the reader part,
I add a line that converts an Array to an ArrayEOC. I do this so I can
use the ArrayEOC methods as mofified since I want to keep Array’s
methods unmodified.
Since I already use self.inherited, I am able to keep track of
descendants (a recursive jaunt through the child classes using inject to
build an array), so there is enum_only{} which enumerates only the given
class, and enum{} which enumerates the given class and all descendants.
I do this because I have a number of units of similar type which follow
the classic inheritance OO model (even though it is apparent from posts
that this has fallen out of favor). Oh well, it seems that I am behind
the times on a lot of things.
Oh, and since Ruby is dynamic, I had to modify remove_method and
undefine_method to remove any comparables, if applicable (there is a
difference between the two however). And if there are subsequent calls
to attr_accessor, I have to add those methods to the class comparables
in the class and any descendant classes. I allow the user to reset the
comparables to those given in the class and its ancestors. I also tried
to make the code reflective, by making the comparables, child_classes,
descndants, etc. availble. Writing this code has certainly been fun and
educational!
js