Easiest way in Ruby to express "given this, set target to this if nilor undefined, else increment by

R

RichardOnRails

The following works, but I'd prefer not to have to class values:

class Z
def self.show_z
@@zz ||= 0; @@zz += 1
puts @@zz
end
end
Z.show_z # => 1
Z.show_z # => 2

I tried this:

$h = Hash.new:)y)
def show_y
$h[:y] ||= 0; $h[:y] += 1 # => Error msg # Line 18
puts $h[:y]
end
show_y
show_y

but it doesn't pass muster with Ruby 1.8.6. I get a complaint about
the first line in show_y, which makes no sense IMHO:

TestOrEqual_operator.rb:18:in `show_y': undefined method `+'
for :y:Symbol (NoMethodError)
from TestOrEqual_operator.rb:21

Also, I'd be able to use a succinct version of this without resorting
to globals.

An ideas?

Thanks in Advance,
Richard
 
J

Jeremy Bopp

The following works, but I'd prefer not to have to class values:

class Z
def self.show_z
@@zz ||= 0; @@zz += 1
puts @@zz
end
end
Z.show_z # => 1
Z.show_z # => 2

Replace the @@'s with @'a so that you use a class instance variable
instead. In this case it works effectively the same while avoiding the
nasty class variables.
I tried this:

$h = Hash.new:)y)
def show_y
$h[:y] ||= 0; $h[:y] += 1 # => Error msg # Line 18
puts $h[:y]
end
show_y
show_y

but it doesn't pass muster with Ruby 1.8.6. I get a complaint about
the first line in show_y, which makes no sense IMHO:

TestOrEqual_operator.rb:18:in `show_y': undefined method `+'
for :y:Symbol (NoMethodError)
from TestOrEqual_operator.rb:21

The error is ultimately the result of the way you initialized your Hash
instance. By passing :y to the new method, you arranged for any
uninitialized key referenced within the hash to have the symbol :y as a
value. Therefore,

$h[:y] ||= 0

doesn't do anything because $h[:y] has a value of :y by default. The
next statement then attempts to increment the value of $h[:y] by 1 and
fails because that value is :y and the + method is not defined for
Symbols. If what I'm saying isn't exactly clear, try this:

h = Hash.new:)howdy)
puts h['Say hello like a Texan!']
puts h['Now say hello like a John Wayne!']

You could avoid this particular problem by simply setting $h to {}
rather than calling Hash.new:)y). But why is a hash necessary for this
at all??? You could just as easily do this:

def show_y
$y ||= 0
$y += 1
puts $y
end

This still leaves you using a global, but it's much simpler. More
importantly, it works. ;-)
Also, I'd be able to use a succinct version of this without resorting
to globals.

Without knowing more about the problem you're trying to solve, it's hard
to provide a better response. Apparently, you want to call a method
that automatically initializes some state when needed and then modify
and preserve that state between subsequent calls to that method. That
state, be it a number as in your examples here or something else, has to
be stored somewhere.

I'm not a fan of globals in general, so I would choose to keep the state
in a class as you did in your first example.

-Jeremy
 
Y

yermej

class Z
    def self.show_z
        @@zz ||= 0; @@zz += 1
        puts @@zz
    end
end
Z.show_z        # => 1
Z.show_z        # => 2

The singleton pattern works well for this sort of thing.

require 'singleton' # this is part of Ruby's standard library
class Counter
include Singleton
def count
@count = (@count ? @count + 1 : 0)
end
end

a = Counter.instance
a.count # => 0
a.count # => 1
b = Counter.instance
b.count # => 2
$h = Hash.new:)y)
def show_y
    $h[:y] ||= 0; $h[:y] += 1 # => Error msg # Line 18
    puts $h[:y]
end
show_y
show_y

And if you really need something like this (hopefully you don't) try:

$h = Hash.new {|the_hash, a_key| the_hash[a_key] = 0}
def show_y
$h[:y] += 1
puts $h[:y]
end

and combining them:

require 'singleton'
class Counters
include Singleton
def initialize
@counts = Hash.new {|h, k| h[k] = 0}
end
def get_count(sym)
@counts[sym] += 1
end
end

c = Counter.instance
c.get_count :y # => 1
c.get_count :y # => 2
c.get_count :z # => 1
 
R

RichardOnRails

The following works,  but I'd prefer not to have to class values:
class Z
    def self.show_z
   @@zz ||= 0; @@zz += 1
   puts @@zz
    end
end
Z.show_z   # => 1
Z.show_z   # => 2

Replace the @@'s with @'a so that you use a class instance variable
instead.  In this case it works effectively the same while avoiding the
nasty class variables.


I tried this:
$h = Hash.new:)y)
def show_y
    $h[:y] ||= 0; $h[:y] += 1 # => Error msg # Line 18
    puts $h[:y]
end
show_y
show_y
but it doesn't pass muster with Ruby 1.8.6.  I get a complaint about
the first line in show_y, which makes no sense IMHO:
TestOrEqual_operator.rb:18:in `show_y': undefined method `+'
for :y:Symbol (NoMethodError)
   from TestOrEqual_operator.rb:21

The error is ultimately the result of the way you initialized your Hash
instance.  By passing :y to the new method, you arranged for any
uninitialized key referenced within the hash to have the symbol :y as a
value.  Therefore,

$h[:y] ||= 0

doesn't do anything because $h[:y] has a value of :y by default.  The
next statement then attempts to increment the value of $h[:y] by 1 and
fails because that value is :y and the + method is not defined for
Symbols.  If what I'm saying isn't exactly clear, try this:

h = Hash.new:)howdy)
puts h['Say hello like a Texan!']
puts h['Now say hello like a John Wayne!']

You could avoid this particular problem by simply setting $h to {}
rather than calling Hash.new:)y).  But why is a hash necessary for this
at all???  You could just as easily do this:

def show_y
  $y ||= 0
  $y += 1
  puts $y
end

This still leaves you using a global, but it's much simpler.  More
importantly, it works. ;-)
Also, I'd be able to use a succinct version of this without resorting
to globals.

Without knowing more about the problem you're trying to solve, it's hard
to provide a better response.  Apparently, you want to call a method
that automatically initializes some state when needed and then modify
and preserve that state between subsequent calls to that method.  That
state, be it a number as in your examples here or something else, has to
be stored somewhere.

I'm not a fan of globals in general, so I would choose to keep the state
in a class as you did in your first example.

-Jeremy

Hi Jeremy,

Thanks for that lucid explanation of where my error lay which in turn
made the + method inappropriate. The app I'm working do have class
and relevant methods defined, so using the scheme I playing with is
fine in a class context.

Again, thanks,
Richard
 
R

RichardOnRails

class Z
    def self.show_z
        @@zz ||= 0; @@zz += 1
        puts @@zz
    end
end
Z.show_z        # => 1
Z.show_z        # => 2

The singleton pattern works well for this sort of thing.

require 'singleton' # this is part of Ruby's standard library
class Counter
  include Singleton
  def count
    @count = (@count ? @count + 1 : 0)
  end
end

a = Counter.instance
a.count # => 0
a.count # => 1
b = Counter.instance
b.count # => 2
$h = Hash.new:)y)
def show_y
    $h[:y] ||= 0; $h[:y] += 1 # => Error msg # Line 18
    puts $h[:y]
end
show_y
show_y

And if you really need something like this (hopefully you don't) try:

$h = Hash.new {|the_hash, a_key| the_hash[a_key] = 0}
def show_y
  $h[:y] += 1
  puts $h[:y]
end

and combining them:

require 'singleton'
class Counters
  include Singleton
  def initialize
    @counts = Hash.new {|h, k| h[k] = 0}
  end
  def get_count(sym)
    @counts[sym] += 1
  end
end

c = Counter.instance
c.get_count :y # => 1
c.get_count :y # => 2
c.get_count :z # => 1

Thanks for introducing me to the Singleton class from the Ruby
library. I'm going to test whether that'll be work best for my
current project.

Best wishes,
Richard
 

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

No members online now.

Forum statistics

Threads
474,444
Messages
2,571,709
Members
48,796
Latest member
Greg L.
Top