views:

171

answers:

5

I want to calculate the amount to charge my customers, when they buy licenses of my product.

I sell it in ranges of licenses:

  • 1-10 : $50/user
  • 11-20 : $40/user
  • 21-30 : $30/user
  • 31-50 : $20/user

So when someone purchases 136 licenses, I will charge him:

50 x 2 x $20 = $2000
30 x 1 x $30 = $900
     6 x $50 = $300

I'm looking for an algorithm on how to process the given number and break it into number of occurrences in a range.. How can I do this in plain C# or LINQ?

------------ EDIT ----------------------------

I started a less confusing question (http://stackoverflow.com/questions/2685391/algorithm-for-fogbugz-pricing-scheme) and I got the answer I've been looking for.

Thank you all..

A: 

A KeyValuePair collection or dictionary maybe?

ChrisBD
A: 

This looks like it would be very similar to algorithms that make change for purchases (which coins to choose). The only difference is that you're comparing against a range instead of a single number.

The code could look something like this:

var val = 136;
var price = 0;
while (val > 0) 
{
  var range = FindMatchingRange(val); // Use a dictionary, list, or array.
  var number = Math.Min(val, range.Max);
  price += range.CostPerUser * number;
  val -= number;
}
John Fisher
+2  A: 

If presented with this price structure I would think that it is in the customer's best interest to minimize the cost by buying the package that best suits their need. The following algorithm uses dynamic programming to calculate the minimal possible price to exactly buy a certain number of licenses (you can save money by buying more than you need, although I haven't implemented that):

int getPrice(int n)
{
    if (n >= 1 && n <= 10) return 50 * n;
    if (n >= 11 && n <= 20) return 40 * n;
    if (n >= 21 && n <= 30) return 30 * n;
    if (n >= 31 && n <= 50) return 20 * n;
    throw new Exception("Impossible");
}

int minimizePrice(int n)
{
    int[] minimumPrice = new int[n + 1];
    for (int i = 1; i <= n; ++i)
    {
        minimumPrice[i] = int.MaxValue;
        for (int j = Math.Max(0, i - 50); j < i; ++j)
        {
            minimumPrice[i] = Math.Min(minimumPrice[i],
                minimumPrice[j] + getPrice(i - j));
        }
    }
    return minimumPrice[n];
}

For 70 licenses the minimal price is $1400 which can be obtained by buying 2 blocks of 35 licenses. You are suggesting a greedy algorithm. This will confuse your customers. A clever customer will place two orders instead of one large order and save $400.

I'd suggest changing your prices so that there is no upper limit to the number of licenses you can buy at $20 each.

Mark Byers
Mark, there's no upper limit.. you can buy as many over 50, but you buy them in batches of 50s, that's it. But I see your point. It looks better if I say >50 = $20..
Anon1865
Mark, I also see your point about greedy algorithm.. I copied from Fogbugz, but now that I think of it you right.. By the way, customers can't combine licenses because is running on a single server.
Anon1865
Anon1865
@Anon1865: Why can't you buy 11 licenses at $40 each? Is your example wrong then?
Mark Byers
From a business standpoint, I don't get the batch idea. If you were selling tomatoes, batches make sense because there is a fixed cost to sending a truck of a certain capacity. For software, there is no such cost: your goal is to sell as many licenses at once. A continuously decreasing unit price makes sense to me.
Mathias
Mathias, the cost in software is in the support.
Anon1865
Mark, I apologize.. Your algorithm works fine... Sorry
Anon1865
@Anon1865 yes but the cost of support is IMO a function of the total number of licenses, not how it breaks in batches. What matters is that you have 60 licenses to support, not 50 licenses + 10 licenses.
Mathias
A: 

If I were a person who needed 10 licenses, under your suggested pricing plan why would I ever buy just 10 licenses?

10 licenses * $50/license = $500

11 licenses * $40/license = $440

What you would want is plan that lowers the cost for the most recently purchased licenses. So that for person wanting 11 licenses they would pay:

(10 licenses * $50/license) + (1 license * $40/license) = $540

A possible plan would look like this:

first 10 licenses (1-10): $50/user
next 10 licenses (11-20): $40/user
next 10 licenses (21-30): $30/user
all licenses after that (31+) : $20/user

Writing code to compute final cost for any number of users is a simple exercise. The calculation for someone purchasing 136 licenses would look like this:

(10 licenses * $50/license) + (10 licenses * $40/license) + (10 licenses * $30/license) + (106 licenses * $20/license) = $500 + $400 + $300 + $2120 = $3,220.

The original pricing plan is wacky, in my opinion. Take the customer who purchased 130 licenses last year who comes back and wants 10 more. What justification is there for charging them the highest rate? They are a high volume customer, you want to sell them (and they justifiably expect to receive) additional licenses at the lowest "marginal" price.

Herbert Sitz
Herbert, the plan was taken from http://www.fogcreek.com/FogBugz/PriceList.html.. It seems like is working for Joel...
Anon1865
Herbert, great comments. In your example, I'm thinking of charging the customer the difference of 50 to 60 licenses for the 10 extra he buys.
Anon1865
The fact that it may work doesn't mean it's optimal. If I'm a client who's previously purchased 50 licenses and I come back and say I want 10 more, I prefer and expect to have you take into account that I'm already a volume purchaser. What is the advantage of this lower-price-depending-on-how-many-you're-buying-at-this-moment (or an alternatively named 'fixed-price-per-arbitrary-volume-grouping') supposed to be, either for the vendor or the customer? I guess I see a small vendor advantage: no need to keep track of customers' previous purchases. As a customer, I dislike that.
Herbert Sitz
A: 

I made a calculation class for you... just more customer-orientated. It calculates the cheapest price possible with your defined priceranges.

Example: 136 Licenses

50 Licenses 20$ each ( because: 31-50 : $20/user )

50 Licenses 20$ each ( because: 31-50 : $20/user )

36 Licenses 20$ each ( because: 31-50 : $20/user )

TOTAL: 1720


Example 130 Licenses

50 Licenses 20$ each

50 Licenses 20$ each

30 Licenses 30$ each

TOTAL: 1900


Code for the class:

   public class PriceCalculator
    {
        public List<OrderPackage> CalculateCheapestPrice(Int32 AmountOfLicenses, 
            List<PriceRange> PriceRanges, out Double Total)
        {
            List<OrderPackage> result = new List<OrderPackage>();
            Total = 0;

            Int32 AmountsOfLicensesleft = AmountOfLicenses;

            PriceRanges.Sort(ComparePrice);

            for (int i = 0; i < PriceRanges.Count; i++)
            {
                for (int j = PriceRanges[i].MaxAmount; j >= PriceRanges[i].MinAmount; j--)
                {
                    if (j <= AmountsOfLicensesleft)
                    {
                        OrderPackage Order = new OrderPackage();
                        Int32 AmountOfThisPackage = AmountsOfLicensesleft / j;
                        //Int32 AmountForThisPrice = Convert.ToInt32(Math.Floor(tmp));

                        Order.PriceRange = PriceRanges[i];
                        Order.AmountOfLicenses = j;

                        Total += Order.AmountOfLicenses * Order.PriceRange.PricePerLicense;

                        for (int k = 0; k < AmountOfThisPackage; k++)
                        {
                            result.Add(Order);
                        }

                        AmountsOfLicensesleft = AmountsOfLicensesleft - (AmountOfThisPackage * j);
                    }
                }
            }

            return result;
        }

        private static int ComparePrice(PriceRange x, PriceRange y)
        {
            if (x.PricePerLicense == y.PricePerLicense)
                return 0;

            if (x.PricePerLicense > y.PricePerLicense)
                return 1;

            if (x.PricePerLicense < y.PricePerLicense)
                return -1;

            return 0;
        }

        public class OrderPackage
        {
            public PriceRange PriceRange { get; set; }
            public Int32 AmountOfLicenses { get; set; }
        }

        public class PriceRange
        {
            public int MinAmount { get; set; }
            public int MaxAmount { get; set; }

            public Double PricePerLicense { get; set; }
        }
    }

Usage example:

private void button1_Click(object sender, EventArgs e)
{
    // Preparing PriceRangeDefinitions
    List<PriceCalculator.PriceRange> PriceRangeDefinitions = new List<PriceCalculator.PriceRange>();
    PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 1, MaxAmount = 10, PricePerLicense = 50 });
    PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 11, MaxAmount = 20, PricePerLicense = 40 });
    PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 21, MaxAmount = 30, PricePerLicense = 30 });
    PriceRangeDefinitions.Add(new PriceCalculator.PriceRange() { MinAmount = 31, MaxAmount = 50, PricePerLicense = 20 });

    // Start the Calculation
    PriceCalculator calculator = new PriceCalculator();
    Double Total;
    List<PriceCalculator.OrderPackage> Packages =
        calculator.CalculateCheapestPrice(130, PriceRangeDefinitions, out Total);

    // Show Proof of Concept
    String ProofOfConcept = String.Empty;
    for (int i = 0; i < Packages.Count; i++)
    {
        ProofOfConcept += Packages[i].AmountOfLicenses.ToString() + " Licenses " +
            Packages[i].PriceRange.PricePerLicense.ToString() + "$ each" + Environment.NewLine;
    }
    ProofOfConcept += Environment.NewLine + "TOTAL: " + Total.ToString();

    MessageBox.Show(ProofOfConcept);
}
Steav
Steav, thanks for the effort man, but this is what I call over-engineering... Check the answer at http://stackoverflow.com/questions/2685391/algorithm-for-fogbugz-pricing-scheme to see how simple can be.
Anon1865