约束编程求解器。
简单而统一的接口,用于多种线性规划和混合整数规划求解,包括 CBC、CLP、GLOP、GLPK、Gurobi、CPLEX 和SCIP。
图算法 (最短路径、最小成本、最大流量、线性求和分配)。
经典旅行推销员问题和车辆路径问题的算法。
经典装箱和背包算法。
安装Google OR-Tools Google OR-Tools的源码在[Github] google/or-tools。其它开发环境下的安装如下。
Linux or Mac下安装
-
确认使用了Python 2.7+,3.5+版本,以及pip 9.0.1+版本。
-
Mac OSX系统需要安装命令行工具Xcode,在Terminal中执行xcode-select --install。 Linux系统需要安装g++,在Terminal中执行sudo apt-get install g++ make。 如果使用C#请确认安装了Mono 4.2.0+的64位版本。
-
在Terminal中执行pip install --upgrade ortools直接安装Python版本的OR-Tools包。C++/Java/C#版本的链接为:Mac, Ubuntu 17.04, Ubuntu 16.04, Ubuntu 14.04, CentOS 7, Debian 9 ,下载到指定目录后执行make all。
Windows下安装 Python版本的包的安装和Linux一样,可自行选用合适的开发工具。若是使用C++、C#,推荐使用64位版本的Windows10操作系统,并且使用Microsoft Visual Studio 2015 或者 2017作为开发工具,相应的库文件下载地址为: Visual Studio 2017 the Visual Studio 2015。
C++使用lib/ortools.lib, 并且将or‑tools/include添加到项目引用。 Java使用jar命令调用lib/com.google.ortools.lib的方式,并且将 ‑Djava.library.path=PATH_TO_or‑tools/lib添加到命令行。 C#添加bin/Google.OrTools.dll到项目依赖,或者使用NuGet搜索Google.OrTools进行安装。 Demo 以下是几种支持语言的demo,运行一下验证是否安装正确。
C++ 代码
复制代码
namespace operations_research { void RunTest( MPSolver::OptimizationProblemType optimization_problem_type) { MPSolver solver("Glop", optimization_problem_type); MPVariable* const x = solver.MakeNumVar(0.0, 1, "x"); MPVariable* const y = solver.MakeNumVar(0.0, 2, "y"); MPObjective* const objective = solver.MutableObjective(); objective->SetCoefficient(x, 1); objective->SetCoefficient(y, 1); objective->SetMaximization(); solver.Solve(); printf("\nSolution:"); printf("\nx = %.1f", x->solution_value()); printf("\ny = %.1f", y->solution_value()); }
void RunExample() { RunTest(MPSolver::GLOP_LINEAR_PROGRAMMING); } }
int main(int argc, char** argv) { operations_research::RunExample(); return 0; } 复制代码
C# 代码
复制代码 using System; using Google.OrTools.LinearSolver;
public class my_program { private static void RunLinearProgrammingExample(String solverType) { Solver solver = Solver.CreateSolver("IntegerProgramming", solverType); Variable x = solver.MakeNumVar(0.0, 1.0, "x"); Variable y = solver.MakeNumVar(0.0, 2.0, "y"); Objective objective = solver.Objective(); objective.SetCoefficient(x, 1); objective.SetCoefficient(y, 1); objective.SetMaximization(); solver.Solve(); Console.WriteLine("Solution:"); Console.WriteLine("x = " + x.SolutionValue()); Console.WriteLine("y = " + y.SolutionValue()); }
static void Main() { RunLinearProgrammingExample("GLOP_LINEAR_PROGRAMMING"); } } 复制代码
Python 代码
复制代码 from future import print_function from ortools.linear_solver import pywraplp
def main(): solver = pywraplp.Solver('SolveSimpleSystem', pywraplp.Solver.GLOP_LINEAR_PROGRAMMING) x = solver.NumVar(0, 1, 'x') y = solver.NumVar(0, 2, 'y') objective = solver.Objective() objective.SetCoefficient(x, 1) objective.SetCoefficient(y, 1) objective.SetMaximization() solver.Solve() print('Solution:') print('x = ', x.solution_value()) print('y = ', y.solution_value())
if name == 'main': main() 复制代码
Java 代码
复制代码 import com.google.ortools.linearsolver.MPConstraint; import com.google.ortools.linearsolver.MPObjective; import com.google.ortools.linearsolver.MPSolver; import com.google.ortools.linearsolver.MPVariable;
public class my_program { static { System.loadLibrary("jniortools"); }
private static MPSolver createSolver (String solverType) { return new MPSolver("my_program", MPSolver.OptimizationProblemType.valueOf(solverType)); }
private static void runmy_program(String solverType, boolean printModel) { MPSolver solver = createSolver(solverType); MPVariable x = solver.makeNumVar(0.0, 1.0, "x"); MPVariable y = solver.makeNumVar(0.0, 2.0, "y"); MPObjective objective = solver.objective(); objective.setCoefficient(y, 1); objective.setMaximization(); solver.solve(); System.out.println("Solution:"); System.out.println("x = " + x.solutionValue()); System.out.println("y = " + y.solutionValue()); }
public static void main(String[] args) throws Exception { runmy_program("GLOP_LINEAR_PROGRAMMING", false); } } 复制代码
执行结果如图:
让大家初步了解了Google Optimization Tools是一款约束求解(CP)的高效套件。那么我们用.Net Core与Google Optimization Tools来实现一个有关员工排班计划的场景感受一下。
众所周知,现实生活中有些工作是7X24工作制的,如呼叫中心或医院护士,最常见的问题就是如何安排多名员工进行倒班,制定好日程时间表,使每班配备足够的人员来维持运营。时间表有各种不同的约束要求,例如:员工不允许连续两次轮班之类。接下来我们介绍类似问题的一个示例,叫护士调度问题,并展示了如何使用.Net Core与Google Optimization Tools实现排班计划。
护士调度问题 在本例中,医院主管需要为四名护士创建一个周时间表,具体情况如下:
每天分为早、中、晚三班轮班。 在每一天,所有护士都被分配到不同的班次,除了有一名护士可以休息。 每位护士每周工作五到六天。 每个班次不会有超过两名护士在工作。 如果一名护士某一天的班次是中班或晚班,她也必须在前一日或次日安排相同的班次。 有两种方式来描述我们需要解决的问题:
指派护士轮班 将班次分配给护士 事实证明,解决问题的最好方法是结合两种方式来求解。
指派护士轮班 下表显示了指派护士轮班视角的排班情况,这些护士被标记为A,B,C,D,换班,编号为0 - 3(其中0表示护士当天不工作)。
星期日
星期一 星期二 星期三 星期四 星期五 星期六 班次1 A B A A A A A 班次2 C C C B B B B 班次3 D D D D C C D 将班次分配给护士 下表显示了将班次分配给护士视角的排班情况。
星期日 星期一 星期二 星期三 星期四 星期五 星期六
护士A 1 0 1 1 1 1 1 护士B 0 1 0 2 2 2 2 护士C 2 2 2 0 3 3 0 护士D 3 3 3 3 0 0 3
.Net Core解决方案 首先使用VS017创建一个.Net Core的控制台项目。
由于Google Optimization Tools对.Net Core的支持还不友好,需要通过NuGet引用一个第三方专门为Core编译好的程序集以及相关依赖,Google.OrTools.Core和CrossPlatformLibraryLoader。
准备完成后,我们逐一介绍编码的过程。首先介绍几个基本概念:
IntVar是约束求解中使用最多的变量形式,一般约束问题中变化的对象都应该定义为一个类似在一定范围内整形数值的变量。 solver.MakeIntVar是创建约束求解中变量的方法,约束求解一定会定义一些可变化的对象,一般都需要转化成数值类型。 solver.Add是添加若干约束条件的方法。 solver.MakePhase定义了求解的目标以及求解的取值策略。 solver.Solve进行求解,并对指定的集合赋值。 solver.MakeAllSolutionCollector表示获取解的集合对象。 定义约束求解器和相关变量 我们用shift和nurse分别来表示班次和护士 。
复制代码 // 创建约束求解器. var solver = new Solver("schedule_shifts"); var num_nurses = 4; var num_shifts = 4; // 班次数定为4,这样序号为0的班次表示是休息的班。 var num_days = 7;
// [START]
// 创建班次变量
var shifts = new Dictionary<(int, int), IntVar>();
foreach (var j in Enumerable.Range(0, num_nurses))
{
foreach (var i in Enumerable.Range(0, num_days))
{
// shifts[(j, i)]表示护士j在第i天的班次,可能的班次的编号范围是:[0, num_shifts)
shifts[(j, i)] = solver.MakeIntVar(0, num_shifts - 1, string.Format("shifts({0},{1})", j, i));
}
}
// 将变量集合转成扁平化数组
var shifts_flat = (from j in Enumerable.Range(0, num_nurses)
from i in Enumerable.Range(0, num_days)
select shifts[(j, i)]).ToArray();
// 创建护士变量
var nurses = new Dictionary<(int, int), IntVar>();
foreach (var j in Enumerable.Range(0, num_shifts))
{
foreach (var i in Enumerable.Range(0, num_days))
{
// nurses[(j, i)]表示班次j在第i天的当班护士,可能的护士的编号范围是:[0, num_nurses)
nurses[(j, i)] = solver.MakeIntVar(0, num_nurses - 1, string.Format("shift{0} day{1}", j, i));
}
}
复制代码 shifts和nurses两个对象含义如下:
shifts[(j, i)]表示护士j在第i天的班次,可能的班次的编号范围是:[0, num_shifts)。 nurses[(j, i)]表示班次j在第i天的当班护士,可能的护士的编号范围是:[0, num_nurses)。 shifts_flat是将shifts的Values简单地处理成扁平化,后面直接用于当参数传给约束求解器solver以指定需要求解的变量。
定义shifts和nurses的对应关系 将每一天的nurses单独列出来,按照编号顺序扁平化成一个数组对象,s.IndexOf(nurses_for_day)是一种OR-Tools要求的特定用法,相当于nurses_for_day求值。这里利用了s的值恰好是在nurses_for_day中对应nurse的编号。注意这里的两层foreach循环,v外层不能互换,必须是现在这样,内层循环的主体对象与shifts_flat一致。
复制代码 // 定义shifts和nurses之前的关联关系 foreach (var day in Enumerable.Range(0, num_days)) { var nurses_for_day = (from j in Enumerable.Range(0, num_shifts) select nurses[(j, day)]).ToArray(); foreach (var j in Enumerable.Range(0, num_nurses)) { var s = shifts[(j, day)]; // s.IndexOf(nurses_for_day)相当于nurses_for_day // 这里利用了s的值恰好是在nurses_for_day中对应nurse的编号 solver.Add(s.IndexOf(nurses_for_day) == j); } } 复制代码
定义护士在不同的班次当班约束 AllDifferent方法是OR-Tools定义约束的方法之一,表示指定的IntVar数组在进行计算时受唯一性制约。满足每一天的当班护士不重复,即每一天的班次不会出现重复的护士的约束条件,同样每一个护士每天不可能同时轮值不同的班次。
复制代码 // 满足每一天的当班护士不重复,每一天的班次不会出现重复的护士的约束条件 // 同样每一个护士每天不可能同时轮值不同的班次 foreach (var i in Enumerable.Range(0, num_days)) { solver.Add((from j in Enumerable.Range(0, num_nurses) select shifts[(j, i)]).ToArray().AllDifferent()); solver.Add((from j in Enumerable.Range(0, num_shifts) select nurses[(j, i)]).ToArray().AllDifferent()); } 复制代码
定义护士每周当班次数的约束 Sum方法是OR-Tools定义运算的方法之一。注意shifts[(j, i)] > 0运算被重载过,其返回类型是WrappedConstraint而不是默认的bool。满足每个护士在一周范围内只出现[5, 6]次。
复制代码 // 满足每个护士在一周范围内只出现[5, 6]次 foreach (var j in Enumerable.Range(0, num_nurses)) { solver.Add((from i in Enumerable.Range(0, num_days) select shifts[(j, i)] > 0).ToArray().Sum() >= 5); solver.Add((from i in Enumerable.Range(0, num_days) select shifts[(j, i)] > 0).ToArray().Sum() <= 6); } 复制代码
定义每个班次在一周内当班护士人数的约束 Max方法是OR-Tools定义运算的方法之一,表示对指定的IntVar数组求最大值。注意MakeBoolVar方法返回类型是IntVar而不是默认的bool,works_shift[(i, j)]为True表示护士i在班次j一周内至少要有1次,BoolVar类型的变量最终取值是0或1,同样也表示了False或True。满足每个班次一周内不会有超过两名护士当班工作。
复制代码 // 创建一个工作的变量,works_shift[(i, j)]为True表示护士i在班次j一周内至少要有1次 // BoolVar类型的变量最终取值是0或1,同样也表示了False或True var works_shift = new Dictionary<(int, int), IntVar>();
foreach (var i in Enumerable.Range(0, num_nurses))
{
foreach (var j in Enumerable.Range(0, num_shifts))
{
works_shift[(i, j)] = solver.MakeBoolVar(string.Format("nurse%d shift%d", i, j));
}
}
foreach (var i in Enumerable.Range(0, num_nurses))
{
foreach (var j in Enumerable.Range(0, num_shifts))
{
// 建立works_shift与shifts的关联关系
// 一周内的值要么为0要么为1,所以Max定义的约束是最大值,恰好也是0或1,1表示至少在每周轮班一天
solver.Add(works_shift[(i, j)] == (from k in Enumerable.Range(0, num_days)
select shifts[(i, k)].IsEqual(j)).ToArray().Max());
}
}
// 对于每个编号不为0的shift, 满足至少每周最多同一个班次2个护士当班
foreach (var j in Enumerable.Range(1, num_shifts - 1))
{
solver.Add((from i in Enumerable.Range(0, num_nurses)
select works_shift[(i, j)]).ToArray().Sum() <= 2);
}
复制代码
定义护士在中班和晚班的连班约束 复制代码 // 满足中班或晚班的护士前一天或后一天也是相同的班次 // 用nurses的key中Tuple类型第1个item的值表示shift为2或3 // shift为1表示早班班次,shift为0表示休息的班次 solver.Add(solver.MakeMax(nurses[(2, 0)] == nurses[(2, 1)], nurses[(2, 1)] == nurses[(2, 2)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 1)] == nurses[(2, 2)], nurses[(2, 2)] == nurses[(2, 3)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 2)] == nurses[(2, 3)], nurses[(2, 3)] == nurses[(2, 4)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 3)] == nurses[(2, 4)], nurses[(2, 4)] == nurses[(2, 5)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 4)] == nurses[(2, 5)], nurses[(2, 5)] == nurses[(2, 6)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 5)] == nurses[(2, 6)], nurses[(2, 6)] == nurses[(2, 0)]) == 1); solver.Add(solver.MakeMax(nurses[(2, 6)] == nurses[(2, 0)], nurses[(2, 0)] == nurses[(2, 1)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 0)] == nurses[(3, 1)], nurses[(3, 1)] == nurses[(3, 2)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 1)] == nurses[(3, 2)], nurses[(3, 2)] == nurses[(3, 3)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 2)] == nurses[(3, 3)], nurses[(3, 3)] == nurses[(3, 4)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 3)] == nurses[(3, 4)], nurses[(3, 4)] == nurses[(3, 5)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 4)] == nurses[(3, 5)], nurses[(3, 5)] == nurses[(3, 6)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 5)] == nurses[(3, 6)], nurses[(3, 6)] == nurses[(3, 0)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 6)] == nurses[(3, 0)], nurses[(3, 0)] == nurses[(3, 1)]) == 1);
复制代码
定义约束求解器的使用 复制代码 // 将变量集合设置为求解的目标,Solver有一系列的枚举值,可以指定求解的选择策略。 var db = solver.MakePhase(shifts_flat, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
// 创建求解的对象
var solution = solver.MakeAssignment();
solution.Add(shifts_flat);
var collector = solver.MakeAllSolutionCollector(solution);
复制代码
执行求解计算并显示结果 复制代码 solver.Solve(db, new[] { collector }); Console.WriteLine("Solutions found: {0}", collector.SolutionCount()); Console.WriteLine("Time: {0}ms", solver.WallTime()); Console.WriteLine();
// 显示一些随机的结果
var a_few_solutions = new[] { 340, 2672, 7054 };
foreach (var sol in a_few_solutions)
{
Console.WriteLine("Solution number {0}", sol);
foreach (var i in Enumerable.Range(0, num_days))
{
Console.WriteLine("Day {0}", i);
foreach (var j in Enumerable.Range(0, num_nurses))
{
Console.WriteLine("Nurse {0} assigned to task {1}", j, collector.Value(sol, shifts[(j, i)]));
}
Console.WriteLine();
}
}
复制代码 运行结果如下:
最后,放出完整的代码如下
复制代码 using Google.OrTools.ConstraintSolver; using System; using System.Collections.Generic; using System.Linq;
public class ConsoleApp1 { static void Main() { // 创建约束求解器. var solver = new Solver("schedule_shifts"); var num_nurses = 4; var num_shifts = 4; // 班次数定为4,这样序号为0的班次表示是休息的班。 var num_days = 7;
// [START]
// 创建班次变量
var shifts = new Dictionary<(int, int), IntVar>();
foreach (var j in Enumerable.Range(0, num_nurses))
{
foreach (var i in Enumerable.Range(0, num_days))
{
// shifts[(j, i)]表示护士j在第i天的班次,可能的班次的编号范围是:[0, num_shifts)
shifts[(j, i)] = solver.MakeIntVar(0, num_shifts - 1, string.Format("shifts({0},{1})", j, i));
}
}
// 将变量集合转成扁平化数组
var shifts_flat = (from j in Enumerable.Range(0, num_nurses)
from i in Enumerable.Range(0, num_days)
select shifts[(j, i)]).ToArray();
// 创建护士变量
var nurses = new Dictionary<(int, int), IntVar>();
foreach (var j in Enumerable.Range(0, num_shifts))
{
foreach (var i in Enumerable.Range(0, num_days))
{
// nurses[(j, i)]表示班次j在第i天的当班护士,可能的护士的编号范围是:[0, num_nurses)
nurses[(j, i)] = solver.MakeIntVar(0, num_nurses - 1, string.Format("shift{0} day{1}", j, i));
}
}
// 定义shifts和nurses之前的关联关系
foreach (var day in Enumerable.Range(0, num_days))
{
var nurses_for_day = (from j in Enumerable.Range(0, num_shifts)
select nurses[(j, day)]).ToArray();
foreach (var j in Enumerable.Range(0, num_nurses))
{
var s = shifts[(j, day)];
// s.IndexOf(nurses_for_day)相当于nurses_for_day
// 这里利用了s的值恰好是在nurses_for_day中对应nurse的编号
solver.Add(s.IndexOf(nurses_for_day) == j);
}
}
// 满足每一天的当班护士不重复,每一天的班次不会出现重复的护士的约束条件
// 同样每一个护士每天不可能同时轮值不同的班次
foreach (var i in Enumerable.Range(0, num_days))
{
solver.Add((from j in Enumerable.Range(0, num_nurses)
select shifts[(j, i)]).ToArray().AllDifferent());
solver.Add((from j in Enumerable.Range(0, num_shifts)
select nurses[(j, i)]).ToArray().AllDifferent());
}
// 满足每个护士在一周范围内只出现[5, 6]次
foreach (var j in Enumerable.Range(0, num_nurses))
{
solver.Add((from i in Enumerable.Range(0, num_days)
select shifts[(j, i)] > 0).ToArray().Sum() >= 5);
solver.Add((from i in Enumerable.Range(0, num_days)
select shifts[(j, i)] > 0).ToArray().Sum() <= 6);
}
// 创建一个工作的变量,works_shift[(i, j)]为True表示护士i在班次j一周内至少要有1次
// BoolVar类型的变量最终取值是0或1,同样也表示了False或True
var works_shift = new Dictionary<(int, int), IntVar>();
foreach (var i in Enumerable.Range(0, num_nurses))
{
foreach (var j in Enumerable.Range(0, num_shifts))
{
works_shift[(i, j)] = solver.MakeBoolVar(string.Format("nurse%d shift%d", i, j));
}
}
foreach (var i in Enumerable.Range(0, num_nurses))
{
foreach (var j in Enumerable.Range(0, num_shifts))
{
// 建立works_shift与shifts的关联关系
// 一周内的值要么为0要么为1,所以Max定义的约束是最大值,恰好也是0或1,1表示至少在每周轮班一天
solver.Add(works_shift[(i, j)] == (from k in Enumerable.Range(0, num_days)
select shifts[(i, k)].IsEqual(j)).ToArray().Max());
}
}
// 对于每个编号不为0的shift, 满足至少每周最多同一个班次2个护士当班
foreach (var j in Enumerable.Range(1, num_shifts - 1))
{
solver.Add((from i in Enumerable.Range(0, num_nurses)
select works_shift[(i, j)]).ToArray().Sum() <= 2);
}
// 满足中班或晚班的护士前一天或后一天也是相同的班次
// 用nurses的key中Tuple类型第1个item的值表示shift为2或3
// shift为1表示早班班次,shift为0表示休息的班次
solver.Add(solver.MakeMax(nurses[(2, 0)] == nurses[(2, 1)], nurses[(2, 1)] == nurses[(2, 2)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 1)] == nurses[(2, 2)], nurses[(2, 2)] == nurses[(2, 3)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 2)] == nurses[(2, 3)], nurses[(2, 3)] == nurses[(2, 4)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 3)] == nurses[(2, 4)], nurses[(2, 4)] == nurses[(2, 5)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 4)] == nurses[(2, 5)], nurses[(2, 5)] == nurses[(2, 6)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 5)] == nurses[(2, 6)], nurses[(2, 6)] == nurses[(2, 0)]) == 1);
solver.Add(solver.MakeMax(nurses[(2, 6)] == nurses[(2, 0)], nurses[(2, 0)] == nurses[(2, 1)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 0)] == nurses[(3, 1)], nurses[(3, 1)] == nurses[(3, 2)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 1)] == nurses[(3, 2)], nurses[(3, 2)] == nurses[(3, 3)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 2)] == nurses[(3, 3)], nurses[(3, 3)] == nurses[(3, 4)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 3)] == nurses[(3, 4)], nurses[(3, 4)] == nurses[(3, 5)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 4)] == nurses[(3, 5)], nurses[(3, 5)] == nurses[(3, 6)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 5)] == nurses[(3, 6)], nurses[(3, 6)] == nurses[(3, 0)]) == 1);
solver.Add(solver.MakeMax(nurses[(3, 6)] == nurses[(3, 0)], nurses[(3, 0)] == nurses[(3, 1)]) == 1);
// 将变量集合设置为求解的目标,Solver有一系列的枚举值,可以指定求解的选择策略。
var db = solver.MakePhase(shifts_flat, Solver.CHOOSE_FIRST_UNBOUND, Solver.ASSIGN_MIN_VALUE);
// 创建求解的对象
var solution = solver.MakeAssignment();
solution.Add(shifts_flat);
var collector = solver.MakeAllSolutionCollector(solution);
solver.Solve(db, new[] { collector });
Console.WriteLine("Solutions found: {0}", collector.SolutionCount());
Console.WriteLine("Time: {0}ms", solver.WallTime());
Console.WriteLine();
// 显示一些随机的结果
var a_few_solutions = new[] { 340, 2672, 7054 };
foreach (var sol in a_few_solutions)
{
Console.WriteLine("Solution number {0}", sol);
foreach (var i in Enumerable.Range(0, num_days))
{
Console.WriteLine("Day {0}", i);
foreach (var j in Enumerable.Range(0, num_nurses))
{
Console.WriteLine("Nurse {0} assigned to task {1}", j, collector.Value(sol, shifts[(j, i)]));
}
Console.WriteLine();
}
}
}
} 复制代码
http://www.cnblogs.com/BeanHsiang/p/8663625.html |