Ruby idiom for enum?

D

David Brady

Okay, one more question from a C++ leopard trying to change his spots:

I want to set up a program that uses some predefined values to determine
its logic. For example, a method could analyze some data and return
"Good", "Fair", "Poor", or "Out of Bounds" based on a set of
thresholds. I want to be able to refer to these values by name in my
code, so constants or symbols make a good choice here. But I also want
to build a sort of rule set around these values: some function returns
values in the range of (0.0..1.0) and I want to be able to say that 0.8
is the minimum score for "Good", etc. I also want the word "Good"
stored in a specific single place so that I don't make any typos each
time I need to print the description.

In C++, I would use an enum for each of the values, then build arrays of
floats and strings indexed by those enums to hold the thresholds and
descriptions.

What's the Ruby idiom for this?

Perhaps Struct followed by some initializer arrays? This seems like a
good start but I end up wanting to build a set of constants first to use
as keys to a hash containing the Structs. Perhaps the Structs should be
the constants themselves, like:

Struct.new("RatingData", name, threshold, description)

RATING_GOOD = Struct::RatingData( :Good, 0.8, "Good Rating" )
RATING_FAIR = Struct::RatingData( :Fair, 0.5, "Fair Rating" )

...etc. The analyze_data could return RATING_GOOD if things were fine. So:

rating = analyze_data data
puts "Analysis: #{rating.description}"

Though this doesn't let me treat the values as though they are ordered, e.g.

puts "*** Warning: Rating below Fair ***" if rating < RATING_FAIR

Thoughts?

-dB
 
R

Robert Klemme

2005/8/28 said:
Okay, one more question from a C++ leopard trying to change his spots:
=20
I want to set up a program that uses some predefined values to determine
its logic. For example, a method could analyze some data and return
"Good", "Fair", "Poor", or "Out of Bounds" based on a set of
thresholds. I want to be able to refer to these values by name in my
code, so constants or symbols make a good choice here. But I also want
to build a sort of rule set around these values: some function returns
values in the range of (0.0..1.0) and I want to be able to say that 0.8
is the minimum score for "Good", etc. I also want the word "Good"
stored in a specific single place so that I don't make any typos each
time I need to print the description.
=20
In C++, I would use an enum for each of the values, then build arrays of
floats and strings indexed by those enums to hold the thresholds and
descriptions.
=20
What's the Ruby idiom for this?
=20
Perhaps Struct followed by some initializer arrays? This seems like a
good start but I end up wanting to build a set of constants first to use
as keys to a hash containing the Structs. Perhaps the Structs should be
the constants themselves, like:
=20
Struct.new("RatingData", name, threshold, description)
=20
RATING_GOOD =3D Struct::RatingData( :Good, 0.8, "Good Rating" )
RATING_FAIR =3D Struct::RatingData( :Fair, 0.5, "Fair Rating" )
=20
...etc. The analyze_data could return RATING_GOOD if things were fine. = So:
=20
rating =3D analyze_data data
puts "Analysis: #{rating.description}"
=20
Though this doesn't let me treat the values as though they are ordered, e= g.
=20
puts "*** Warning: Rating below Fair ***" if rating < RATING_FAIR
=20
Thoughts?

There are several possible solutions: Use an enum lib maybe there's
something in the RAA
http://raa.ruby-lang.org/search.rhtml?search=3Denum

Or create a Hash with nested Arrays

RATINGS =3D {
:good =3D> [0.8, "Good Rating"],
:bad =3D> [0.1, "Bad Rating"]
}

I'd probably go with your struct approach with some additional code
for creation of a single rating that will also register all ratings
somewhere and with some algorithm for determining the rating.

For example, if you place your constants in class Struct::RatingData
you can easily get all ratings via Struct::RatingData.constants.

If you need efficient rating assignment from a float value and if
there are many ratings then you might want to store ratings ordered by
the threshold to use binary search on the ratings to find the correct
one.

Just some thoughts.

Kind regards

robert
 
B

Brian Schröder

Okay, one more question from a C++ leopard trying to change his spots:
=20
I want to set up a program that uses some predefined values to determine
its logic. For example, a method could analyze some data and return
"Good", "Fair", "Poor", or "Out of Bounds" based on a set of
thresholds. I want to be able to refer to these values by name in my
code, so constants or symbols make a good choice here. But I also want
to build a sort of rule set around these values: some function returns
values in the range of (0.0..1.0) and I want to be able to say that 0.8
is the minimum score for "Good", etc. I also want the word "Good"
stored in a specific single place so that I don't make any typos each
time I need to print the description.
=20
In C++, I would use an enum for each of the values, then build arrays of
floats and strings indexed by those enums to hold the thresholds and
descriptions.
=20
What's the Ruby idiom for this?
=20
Perhaps Struct followed by some initializer arrays? This seems like a
good start but I end up wanting to build a set of constants first to use
as keys to a hash containing the Structs. Perhaps the Structs should be
the constants themselves, like:
=20
Struct.new("RatingData", name, threshold, description)
=20
RATING_GOOD =3D Struct::RatingData( :Good, 0.8, "Good Rating" )
RATING_FAIR =3D Struct::RatingData( :Fair, 0.5, "Fair Rating" )
=20
...etc. The analyze_data could return RATING_GOOD if things were fine. = So:
=20
rating =3D analyze_data data
puts "Analysis: #{rating.description}"
=20
Though this doesn't let me treat the values as though they are ordered, e= g.
=20
puts "*** Warning: Rating below Fair ***" if rating < RATING_FAIR
=20
Thoughts?
=20

Simply expanding on your thoughts:

--------8<--------8<--------
module RATING
RatingData =3D Struct.new:)name, :threshold, :description)

class RatingData
include Comparable

def <=3D>(o)
if o.respond_to?:threshold
=09self.threshold <=3D> o.threshold
else
=09self.threshold <=3D> o
end
end
end
=20
GOOD =3D RatingData.new( :Good, 0.8, "Good Rating" )
FAIR =3D RatingData.new( :Fair, 0.5, "Fair Rating" )
POOR =3D RatingData.new( :poor, 0.2, "Poor Rating" )
end

rating =3D RATING::pOOR
puts "Analysis: #{rating.description}"

puts "*** Warning: Rating below Fair ***" if rating < RATING::FAIR
--------8<--------8<--------

would be a possibility. Throw in some metaprogramming and it can get
really nice.

regards,

Brian

--=20
http://ruby.brian-schroeder.de/

Stringed instrument chords: http://chordlist.brian-schroeder.de/
 
A

Ara.T.Howard

Okay, one more question from a C++ leopard trying to change his spots:

I want to set up a program that uses some predefined values to determine its
logic. For example, a method could analyze some data and return "Good",
"Fair", "Poor", or "Out of Bounds" based on a set of thresholds. I want to
be able to refer to these values by name in my code, so constants or symbols
make a good choice here. But I also want to build a sort of rule set around
these values: some function returns values in the range of (0.0..1.0) and I
want to be able to say that 0.8 is the minimum score for "Good", etc. I
also want the word "Good" stored in a specific single place so that I don't
make any typos each time I need to print the description.

In C++, I would use an enum for each of the values, then build arrays of
floats and strings indexed by those enums to hold the thresholds and
descriptions.

What's the Ruby idiom for this?

you could use enums : it's easy enough to do clearly in ruby

http://groups.google.com/group/comp...e36?q=ara+howard+enum&rnum=1#ea5a7cc77156ee36
Perhaps Struct followed by some initializer arrays? This seems like a good
start but I end up wanting to build a set of constants first to use as keys
to a hash containing the Structs. Perhaps the Structs should be the
constants themselves, like:

Struct.new("RatingData", name, threshold, description)

RATING_GOOD = Struct::RatingData( :Good, 0.8, "Good Rating" )
RATING_FAIR = Struct::RatingData( :Fair, 0.5, "Fair Rating" )

...etc. The analyze_data could return RATING_GOOD if things were fine. So:

rating = analyze_data data
puts "Analysis: #{rating.description}"

Though this doesn't let me treat the values as though they are ordered, e.g.

i often do something like this:

harp:~ > cat a.rb
class C
module RATING
LIST = [
GOOD = 'GOOD',
FAIR = 'FAIR',
POOR = 'POOR',
]
end
RAITINGS = RATING::LIST
end

p C::RATING::GOOD
p C::RAITINGS
C::RAITINGS.each{|r| p r}


harp:~ > ruby a.rb
"GOOD"
["GOOD", "FAIR", "POOR"]
"GOOD"
"FAIR"
"POOR"

remember - the main reason to use ints for enums in c/c++ is to be able to use
'==' with the variables but a language like ruby can do just as well with
strings.

puts "*** Warning: Rating below Fair ***" if rating < RATING_FAIR

Thoughts?

in any case, reading over your description i'd be inclined to wrap Raitings as
as objects and design a class generator that sets appropriate constants to
configurable raitings, something like:

harp:~ > cat a.rb
module RuleSet
class Raiting
include Comparable
attr 'description'
attr 'value'
def initialize d, v
@description, @value = String(d), Float(v)
end
def <=> other
ov = other.value rescue other
value <=> ov
end
def to_s
"#{ description }(#{ value })"
end
alias inspect to_s
end

module ClassMehods
attr 'raitings'
def inspect; raitings.inspect; end
def to_s; raitings.inspect; end
end

module InstanceMethods
end

class << self
def new(spec = {'good' => 0.8, 'fair' => 0.5, 'poor' => 0.2,})
klass = Class::new {
include InstanceMethods
extend ClassMehods
@raitings = []
%w( good fair poor ).each do |k|
c = k.upcase
ks = k.to_s
ki = ks.intern
keys = [k, ki, ks.downcase, ks.upcase]
v = nil
keys.each{|k| v = spec[k] and break}
r = Raiting::new k, v
const_set c, r
@raitings << r
end
}
end
alias [] new
end
end

rule_set = RuleSet::new
p rule_set::raitings
p(rule_set::GOOD < 42)
p rule_set::GOOD.description
p rule_set::GOOD.value


rs =
RuleSet[
:good => 42,
:fair => 41,
:poor => 40,
]
p rs
p(rs::GOOD == 42)


harp:~ > ruby a.rb
[good(0.8), fair(0.5), poor(0.2)]
true
"good"
0.8
[good(42.0), fair(41.0), poor(40.0)]
true


hth.

-a
--
===============================================================================
| email :: ara [dot] t [dot] howard [at] noaa [dot] gov
| phone :: 303.497.6469
| Your life dwells amoung the causes of death
| Like a lamp standing in a strong breeze. --Nagarjuna
===============================================================================
 
F

Florian Groß

David said:
In C++, I would use an enum for each of the values, then build arrays of
floats and strings indexed by those enums to hold the thresholds and
descriptions.

That's also possible in Ruby, but I think it is a better idea to
aggregate all the data into the enum member itself.

Here's Ruby code for doing C++ style enums:
# Represents a C# style enumeration of known values.
#
# Usage:
# Color = Enum.new:)Red, :Green, :Blue)
# Color.is_a?(Enum) # => true
# Color::Red.inspect # => "Color::Red"
# Color::Green.is_a?(Color) # => true
# Color::Green.is_a?(Enum::Member) # => true
# Color::Green.index # => 1
# Color::Blue.enum # => Color
# values = [[255, 0, 0], [0, 255, 0], [0, 0, 255]]
# values[Color::Green] # => [0, 255, 0]
# Color[0] # => Color::Red
# Color.size # => 3
#
# Enums are enumerable. Enum::Members are comparable.
class Enum < Module
class Member < Module
attr_reader :enum, :index

def initialize(enum, index)
@enum, @index = enum, index
# Allow Color::Red.is_a?(Color)
extend enum
end

# Allow use of enum members as array indices
alias :to_int :index
alias :to_i :index

# Allow comparison by index
def <=>(other)
@index <=> other.index
end

include Comparable
end

def initialize(*symbols, &block)
@members = []
symbols.each_with_index do |symbol, index|
# Allow Enum.new:)foo)
symbol = symbol.to_s.sub(/^[a-z]/) { |letter| letter.upcase }.to_sym
member = Enum::Member.new(self, index)
const_set(symbol, member)
@members << member
end
super(&block)
end

def [](index) @members[index] end
def size() @members.size end
alias :length :size

def first(*args) @members.first(*args) end
def last(*args) @members.last(*args) end

def each(&block) @members.each(&block) end
include Enumerable
end

But I think it is not the best match in this case.
Struct.new("RatingData", name, threshold, description)

RATING_GOOD = Struct::RatingData( :Good, 0.8, "Good Rating" )
RATING_FAIR = Struct::RatingData( :Fair, 0.5, "Fair Rating" )

This sounds good, but I'd go with a module:

module Rating
Good = Struct::RatingData.new:)Good, 0.8, "Good Rating")
Fair = Struct::RatingData.new:)Fair, 0.5, "Fair Rating")
end

That seems more Rubyish to me.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top