2010-04-21 15 views
6

Estoy buscando un algoritmo para calcular el costo total de las licencias adquiridas según el esquema de precios "FogBugz para su servidor" (http://www.fogcreek.com/FogBugz/PriceList.html).Algoritmo del esquema de precios de Fogbugz

precios FogBugz es:

  • 1 Licencia $ 299
  • 5 Paquete de licencia de $ 999
  • 10 Licencia para paquete de $ 1.899
  • 20 Licencia para paquete de $ 3.499
  • 50 Licencia para paquete de $ 7.999

Si solicita un presupuesto, digamos 136 licencias, calculó lo comió como $ 22,694.

¿Cómo puedo hacer esto en C# o LINQ?

Cualquier ayuda será apreciada.

+0

¿En qué exactamente necesita ayuda?¿Estás tratando de descubrir cómo escogen el esquema de precios apropiado para los valores que están por encima de los paquetes estándar (más de 51 licencias)? –

+3

Es una pregunta extraña (o una manera de hacerlo con una referencia específica a FogBugz ...) pero no estoy seguro de que deba ser rechazada ... – mmacaulay

+2

Consulte la [pregunta previa] del OP (http://stackoverflow.com/questions/2684261/how-to-convert-a-number-to-a-range-of-prices). –

Respuesta

7

la respuesta aceptada, mientras que una elegante pieza de código desde el punto de vista de un programador, no da el mejor precio posible para el cliente y, por tanto, podría no ser una solución elegante desde el punto de vista del cliente. Por ejemplo, cuando n = 4, la respuesta aceptada da $ 1196, pero un cliente obviamente preferiría elegir el paquete de 5 licencias y pagar solo $ 999 en su lugar.

Es posible construir un algoritmo que pueda calcular el precio mínimo posible que el cliente puede pagar para comprar el número requerido de licencias. Una forma de hacerlo es usar programación dinámica. Creo que algo como esto podría hacer el truco:

int calculatePrice(int n, Dictionary<int, int> prices) 
{ 

    int[] best = new int[n + prices.Keys.Max()]; 
    for (int i = 1; i < best.Length; ++i) 
    { 
     best[i] = int.MaxValue; 
     foreach (int amount in prices.Keys.Where(x => x <= i)) 
     { 
      best[i] = Math.Min(best[i], 
       best[i - amount] + prices[amount]); 
     } 
    } 
    return best.Skip(n).Min(); 
} 

void Run() 
{ 
    Dictionary<int, int> prices = new Dictionary<int, int> { 
     { 1, 299 }, 
     { 5, 999 }, 
     { 10, 1899 }, 
     { 20, 3499 }, 
     { 50, 7999 } 
    }; 

    Console.WriteLine(calculatePrice(136, prices)); 
    Console.WriteLine(calculatePrice(4, prices)); 
} 

Salida:

22694 
999 

actualización Producir un desglose es un poco más complicado, pero definitivamente creo que va a ser beneficioso para sus clientes. Usted podría hacer algo como esto (suponiendo que la impresión de la consola, aunque un programa real sería probablemente de salida a una página web):

using System; 
using System.Linq; 
using System.Collections.Generic; 

class Program 
{ 
    static Dictionary<int, int> prices = new Dictionary<int, int> { 
      { 1, 299 }, 
      { 5, 999 }, 
      { 10, 1899 }, 
      { 20, 3499 }, 
      { 50, 7999 } 
    }; 

    class Bundle 
    { 
     public int Price; 
     public Dictionary<int, int> Licenses; 
    } 

    Bundle getBestBundle(int n, Dictionary<int, int> prices) 
    { 
     Bundle[] best = new Bundle[n + prices.Keys.Max()]; 
     best[0] = new Bundle 
     { 
      Price = 0, 
      Licenses = new Dictionary<int, int>() 
     }; 

     for (int i = 1; i < best.Length; ++i) 
     { 
      best[i] = null; 
      foreach (int amount in prices.Keys.Where(x => x <= i)) 
      { 
       Bundle bundle = new Bundle 
       { 
        Price = best[i - amount].Price + prices[amount], 
        Licenses = new Dictionary<int,int>(best[i - amount].Licenses) 
       }; 

       int count = 0; 
       bundle.Licenses.TryGetValue(amount, out count); 
       bundle.Licenses[amount] = count + 1; 

       if (best[i] == null || best[i].Price > bundle.Price) 
       { 
        best[i] = bundle; 
       } 
      } 
     } 
     return best.Skip(n).OrderBy(x => x.Price).First(); 
    } 

    void printBreakdown(Bundle bundle) 
    { 
     foreach (var kvp in bundle.Licenses) { 
      Console.WriteLine("{0,2} * {1,2} {2,-5} @ ${3,4} = ${4,6}", 
       kvp.Value, 
       kvp.Key, 
       kvp.Key == 1 ? "user" : "users", 
       prices[kvp.Key], 
       kvp.Value * prices[kvp.Key]); 
     } 

     int totalUsers = bundle.Licenses.Sum(kvp => kvp.Key * kvp.Value); 

     Console.WriteLine("-------------------------------"); 
     Console.WriteLine("{0,7} {1,-5}   ${2,6}", 
      totalUsers, 
      totalUsers == 1 ? "user" : "users", 
      bundle.Price); 
    } 

    void Run() 
    { 
     Console.WriteLine("n = 136"); 
     Console.WriteLine(); 
     printBreakdown(getBestBundle(136, prices)); 
     Console.WriteLine(); 
     Console.WriteLine(); 
     Console.WriteLine("n = 4"); 
     Console.WriteLine(); 
     printBreakdown(getBestBundle(4, prices)); 
    } 

    static void Main(string[] args) 
    { 
     new Program().Run(); 
    } 
} 

Salida:

n = 136 

2 * 50 users @ $7999 = $ 15998 
1 * 20 users @ $3499 = $ 3499 
1 * 10 users @ $1899 = $ 1899 
1 * 5 users @ $ 999 = $ 999 
1 * 1 user @ $ 299 = $ 299 
------------------------------- 
    136 users   $ 22694 


n = 4 

1 * 5 users @ $ 999 = $ 999 
------------------------------- 
     5 users   $ 999 
+0

Mark, esta es una excelente idea ... Si pudiera obtener el desglose de la solución propuesta, entonces puedo explicarle al cliente por qué es más barato para ellos , será impresionante. – Anon1865

+0

@ Anon1865: Se agregó un ejemplo de cómo se puede desglosar al cliente. Para mostrar por qué es más económico, debe tener algo con lo que compararlo. Podría crear un desglose similar para el otro algoritmo y mostrar cuánto ahorran, o quizás intentar anunciar que les está otorgando 5 licencias en lugar de 4 sin costo adicional. De todos modos, con suerte te di suficiente para comenzar. –

+0

Esto parece excesivo. Estoy bastante seguro de que un simple esquema codicioso funciona bien para este horario de precios si solo establece los límites de forma correcta. –

11
int licenses = 136; 
int sum = 0; 

while (licenses > 0) 
{ 
    if (licenses >= 50)  { sum += 7999; licenses -= 50; } 
    else if (licenses >= 20) { sum += 3499; licenses -= 20; } 
    else if (licenses >= 10) { sum += 1899; licenses -= 10; } 
    else if (licenses >= 5) { sum += 999; licenses -= 5; } 
    else      { sum += 299; licenses -= 1; } 
} 

// sum == 22694 

o

int licenses = 136; 
int sum = 7999 * Math.DivRem(licenses, 50, out licenses) 
     + 3499 * Math.DivRem(licenses, 20, out licenses) 
     + 1899 * Math.DivRem(licenses, 10, out licenses) 
     + 999 * Math.DivRem(licenses, 5, out licenses) 
     + 299 * licenses; 

// sum == 22694 
+0

Solo intentaba tipear una respuesta similar. –

+1

+1, aunque un algoritmo basado en divisiones sería más eficiente para aquellos clientes que compran millones de licencias;) –

+8

@Kent Boogaart: la respuesta se amplió para acomodarse a una gran cantidad de licencias. Cambia 'int' a' long' para giga-squillions de licencias. – dtb

1

solución de Mark es una gran solución general y definitivamente lo que hay que ir con (en caso de que los precios nunca cambian.) Esta solución combina la simplicidad de la DTB con la corrección de la marca de:

int licenses = 136; 
int sum = 7999 * Math.DivRem(licenses, 50, out licenses) 
     + 7999 * Math.DivRem(licenses, 46, out licenses) 
     + 3499 * Math.DivRem(licenses, 20, out licenses) 
     + 1899 * Math.DivRem(licenses, 10, out licenses) 
     + 999 * Math.DivRem(licenses, 5, out licenses) 
     + 999 * Math.DivRem(licenses, 4, out licenses) 
     + 299 * licenses; 

parece que la ONL y los casos de borde son 5 es mejor que 4, y 50 es mejor que 46 ... 49. Aunque, de manera realista, probablemente debas sugerir 50 cuando alguien busque 45, ya que las 5 licencias adicionales solo cuestan $ 2. Por lo tanto, tal vez chnage 46 a 45 en el código.