Consolidating integers within Ruby (on rails)

77 Views Asked by At

I have a class which contains two integers (a and b) and a reference to an entity.

I want to be able to report back on the sum of all the a's and b's within the class.

E.g. I might have three records:

Entity    1  2  3 
a         5  9  3 
b         8  1  2

I want my consolidated result for a to be 17, and for b 11.

I'm interested in view on the best approach to this. Now I could sum everything on the fly, but if there are lots of records that might be slow.

Or I could maintain a fake entity which contains the consolidated result and is updated each time any of the other objects are update.

Or I could have a different class for consolidated date?

All thoughts appreciated.

Thanks Chris

3

There are 3 best solutions below

0
On

I see two ways you might choose from:

  1. compute the sum of a and b eagerly, always when any of these change
  2. compute lazily, only when someone needs the consolidated value

You should know which model of the above fits your needs more. I would put the eager way the following way:

class ABCounter

  @consolidated_apples = 0
  @consolidated_pears = 0

  class << self
    attr_accessor :consolidated_apples, :consolidated_pears
  end

  attr_accessor :apples, :pears
  def initialize
    @apples = 0
    @pears = 0
  end

  def apples=(x)
    ABCounter.consolidated_apples += x - @apples
    @apples = x
  end

  def pears=(x)
    ABCounter.consolidated_pears += x - @pears
    @pears = x
  end
end

The lazy way should go through all created instances of ABCounter and sum up the values of a and b.

0
On

The simplest way is to track this information in the class. For example, suppose we have a number of FruitBaskets that may contain any number of apples and bananas. At any moment we want to know the total number of apples and bananas in all the baskets.

module FruitCounter
  attr_accessor :apples, :bananas

  def apples; @apples ||= 0; end
  def bananas; @bananas ||= 0; end
end

class FruitBasket
  class << self
    include FruitCounter   # Keeps track of the total for all FruitBaskets.
  end

  include FruitCounter

  def apples=(v)
    d = v - self.apples       # Note the difference.
    @apples = v               # Set the new value for this instance.
    self.class.apples += d    # Adjust the total by the difference.
  end

  def bananas=(v)
    d = v - self.bananas
    @bananas = v
    self.class.bananas += d
  end
end

Let's see it in action:

first = FruitBasket.new
 => #<FruitBasket:0x97be6f8> 
first.apples = 10; first.bananas = 15

FruitBasket.apples
 => 10 
FruitBasket.bananas
 => 15

So far, so good. How about another basket?

second = FruitBasket.new
 => #<FruitBasket:0x97b28e4> 
second.apples = 30; second.bananas = 20

FruitBasket.apples
 => 40 
FruitBasket.apples == first.apples + second.apples
 => true 

And now let's modify the contents of the first basket:

first.apples = 3
 => 3 
FruitBasket.apples
 => 33
FruitBasket.apples == first.apples + second.apples
 => true 

There you go!

0
On

I think you need a class method, something like:

def self.consolidated_a
  if @@cache_invalid
    @@cached_a = all.collect(0) { |sum, x| sum + x.a }
  end
  @@cached_a
end

If you want it cached, you could have a class variable called @@cache_invalid for example, and set this to true any time a or b change. Then you could check this, and return a cached value if false, and run the code above if true (I've now edited the code to include this change).