Solving a Knapsack problem with Solver Foundation and LINQ

On an internal message board, somebody asked how to solve the following problem:

Let’s say I have this list of days and prices:

List<ReservationPrice> prices = new List<ReservationPrice>(); 
prices.Add(new ReservationPrice { NumberOfDays = 1, Price = 1000 }); 
prices.Add(new ReservationPrice { NumberOfDays = 2, Price = 1200 }); 
prices.Add(new ReservationPrice { NumberOfDays = 3, Price = 2500 }); 
prices.Add(new ReservationPrice { NumberOfDays = 4, Price = 3100 }); 
prices.Add(new ReservationPrice { NumberOfDays = 7, Price = 4000 }); 

What I would like to able to do now is: give me the best price from the list based on a number of days.

So if ask for 3 days the best price from the list is from child one (1000) and two (1200), but there are of course different combinations. How would an algorithm that found the best price from this list look like ?

Here’s how you could do it in Solver Foundation. You’ll note that the way we bind the solution (which items to choose) back to the ReservationPrice data structure kind of stinks. Other than that the code is pretty straightforward.

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.SolverFoundation.Services;

namespace SolverFoundationSample {

  public class ReservationPrice {
    private double _selectedValue;

    public int Id { get; set; }
    public int NumberOfDays { get; set; }
    public int Price { get; set; }
    
    public bool Selected {
      get { return _selectedValue > 0.0; }
      set { _selectedValue = value ? 1.0 : 0.0; }
    }
    
    // It's too bad that we need this field. It would be nicer to bind
    // the Decision to the Selected property - but we need a double-valued
    // property.
    public double SelectedValue {
      get { return _selectedValue; }
      set { _selectedValue = value; }
    }

    public override string ToString() {
      return "Number of days = " + NumberOfDays + ", price = " + Price;
    }
  }

  public class Knapsack {

    public static void Main(string[] args) {
      SolveKnapsack();
    }

    private static IEnumerable<ReservationPrice> GetData() {
      List<ReservationPrice> prices = new List<ReservationPrice>();
      prices.Add(new ReservationPrice { NumberOfDays = 1, Price = 1000, Id = 0 });
      prices.Add(new ReservationPrice { NumberOfDays = 2, Price = 1200, Id = 1 });
      prices.Add(new ReservationPrice { NumberOfDays = 3, Price = 2500, Id = 2 });
      prices.Add(new ReservationPrice { NumberOfDays = 4, Price = 3100, Id = 3 });
      prices.Add(new ReservationPrice { NumberOfDays = 7, Price = 4000, Id = 4 });
      return prices;
    }

    private static void SolveKnapsack() {
      SolverContext context = SolverContext.GetContext();
      Model model = context.CreateModel();
      var items = new Set(Domain.Any, "items");

      var theData = GetData();

      // Duration of each reservation
      var length = new Parameter(Domain.IntegerNonnegative, "length", items);
      length.SetBinding(theData, "NumberOfDays", "Id");
      model.AddParameter(length);

      // Price for each reservation.
      var price = new Parameter(Domain.IntegerNonnegative, "price", items);
      price.SetBinding(theData, "Price", "Id");
      model.AddParameter(price);

      // The duration requirement.
      int duration = 3;

      var choose = new Decision(Domain.IntegerRange(0, 1), "choose", items);
      choose.SetBinding(theData, "SelectedValue", "Id");
      model.AddDecision(choose);
      
      // Reserve the right number of days.
      model.AddConstraint("c", (Model.Sum(Model.ForEach(items, i => (choose[i] * length[i]))) == duration));

      // Spend as little as possible.
      model.AddGoal("g", GoalKind.Minimize, Model.Sum(Model.ForEach(items, i => (price[i] * choose[i]))));

      var solution = context.Solve();
      context.PropagateDecisions();

      var selected = from d in theData
                where d.Selected
                select d.ToString();

      foreach (var s in selected) {
        Console.WriteLine(s);
      }
    }
  }
}

Author: natebrix

Follow me on twitter at @natebrix.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s