Ruby idiom for enum?

(David Brady) #1

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

···

--
David Brady
ruby-talk@shinybit.com
I'm having a really surreal day... OR AM I?

(Robert K.) #2

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

Or create a Hash with nested Arrays

RATINGS = {
  :good => [0.8, "Good Rating"],
  :bad => [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

···

2005/8/28, David Brady <ruby_talk@shinybit.com>:

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?

(Brian Schröder) #3

Simply expanding on your thoughts:

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

  class RatingData
    include Comparable

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

rating = 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

···

On 28/08/05, David Brady <ruby_talk@shinybit.com> wrote:

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?

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

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

(Ara.T.Howard) #4

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.lang.ruby/browse_thread/thread/6f37e998434e65e5/ea5a7cc77156ee36?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

···

On Sun, 28 Aug 2005, David Brady wrote:
--

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

===============================================================================

(Florian Gross) #5

David Brady wrote:

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.

(Patrick Hurley) #6

Try this link:

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/150439

See if it helps

pth

···

On 8/28/05, Brian Schröder <ruby.brian@gmail.com> wrote:

On 28/08/05, David Brady <ruby_talk@shinybit.com> wrote:
> 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?
>