0

I am trying to create a method to calculate the shipping cost for a package. The shipping cost comes in a table which states the weight range and the corresponding price.

Example data:

0 - 50g : $1
51 - 100g : $2

How should I structure my data, how can I generate the price dynamically based on the data instead of writing many if statements?

Example code:

def price_for_weight(w)    
  if w < 50
    return 1
  elsif w < 100
    return 2
  end
sawa
  • 165,429
  • 45
  • 277
  • 381
harinsa
  • 3,176
  • 5
  • 33
  • 53
  • about 30 categories, There is no direct relationship between weight and price e.g. should be able to specify price for each category. – harinsa Dec 23 '15 at 03:55
  • side note: in Ruby you don't need to specify `return` unless you have an expression that would terminate the execution of the method prematurely. Ruby methods automatically return the return value of the last evaluated expression. – sixty4bit Dec 23 '15 at 04:02
  • 2
    @sixty4bit thanks, I just like to see the word return, gives me assurance haha. – harinsa Dec 23 '15 at 04:05
  • what is the price if weight is 50.5g? – Wand Maker Dec 23 '15 at 06:08
  • @WandMaker good catch! – harinsa Dec 23 '15 at 06:15

3 Answers3

2

One way you could do this is to structure your data as a Hash with Range objects as the keys like so:

SHIPPING_COSTS = {
  0..50 => 1,
  51..100 => 2,
}

Using Enumerable#select in your method, you can then get a key-value pair using the === operator:

 def price_for_weight(w)    
    SHIPPING_COSTS.select { |range| range === w }
 end

=== used with a set asks whether the value on the right side of the comparison can be considered a member of the set on the left side (see this question).

Since the above would return a key-value pair, you could add .values.first after the block to get just the cost:

 def price_for_weight(w)    
    SHIPPING_COSTS.select { |range| range === w }.values.first
 end
Community
  • 1
  • 1
sixty4bit
  • 7,422
  • 7
  • 33
  • 57
1

Assuming the costs are not always whole dollars, counting in cents is better.

ShippingCost = {50 => 100, 100 => 200}

def price_for_weight(w)
  ShippingCost[ShippingCost.keys.bsearch{|k| w <= k}]
end
sawa
  • 165,429
  • 45
  • 277
  • 381
0

Here is one more way to do this:

Given that your ranges are coming from DB, you will be using an array of something. We could use a hash to represent values of each price range as shown below.

prices = [ {min: 0, max: 50, price: 1}, {min: 50, max: 100, price: 2} ]
weight = 50.5

prices.find {|i| (i[:min]...i[:max]).include? weight }[:price]
#=> 2

Also note the usage of ... in range, which excludes the end value. Hence, (0...50) will not include 50. This will ensure that there is no overlap between ranges in case you had used (0..50), (50..100), or we don't skip a range of values if you had used (0..50) and (51..100).

Wand Maker
  • 18,476
  • 8
  • 53
  • 87