我构建了一个应用程序,用于模拟公司每月可以以不同“模式”生产的产品数量。此模拟用于帮助找到最佳运行模式系列一个月,以最好地满足当月的预计销售预测。该应用程序一直运行良好,直到最近工厂被修改为以其他模式运行。现在可以以16种模式运行。对于22个工作日的一个月,这产生了9,364,199,760种可能的组合。这从过去的8种模式中提升,仅产生1,560,780种可能的组合。运行此应用程序的PC是旧的,在抛出内存不足异常之前无法处理计算次数。事实上,整个应用程序不能支持超过15种模式,因为它使用整数来跟踪模式的数量,并且它超过了整数的上限。面对这个问题,我需要尽我所能降低应用程序的内存利用率并优化它以尽可能高效地运行,即使它无法实现16种模式的既定目标。我正在考虑将数据写入磁盘而不是将列表存储在内存中,但在我承担这一开销之前,我希望得到人们对该方法的看法,看看是否有任何优化空间。
修改 基于少数人的建议,考虑更具学术性的东西,然后只计算每个可能的答案,下面列出了如何选择最佳运行(模式组合)的简要说明。 目前,计算机确定工厂可以在该月的工作日数内运行的每种可能方式。例如,3个模式最多2个工作日将导致(1,1),(1,2),(1,3),(2,2)的组合(其中数字代表所选模式), (2,3),(3,3)对于每种模式,产品以不同的生产率生产,例如在模式1中,产品x可以每小时50个单位生产,其中产品y以每小时30个单位生产,产品z以每小时0个单位产生。然后将每个组合乘以工时和生产率。选择产生与每月产品的预测值最接近匹配的数字的运行。但是,由于工厂的某些月份不符合产品的预测值,因此该算法会提高下个月产品的优先级,以确保产品在年底达到预测值。由于仓库空间紧张,重要的是产品不要过多生产。
谢谢
private List<List<int>> _modeIterations = new List<List<int>>();
private void CalculateCombinations(int modes, int workDays, string combinationValues)
{
List<int> _tempList = new List<int>();
if (modes == 1)
{
combinationValues += Convert.ToString(workDays);
string[] _combinations = combinationValues.Split(',');
foreach (string _number in _combinations)
{
_tempList.Add(Convert.ToInt32(_number));
}
_modeIterations.Add(_tempList);
}
else
{
for (int i = workDays + 1; --i >= 0; )
{
CalculateCombinations(modes - 1, workDays - i, combinationValues + i + ",");
}
}
}
答案 0 :(得分:10)
这种优化问题困难但非常充分研究。您可能应该阅读有关它的文献,而不是试图重新发明轮子。您要查找的关键字是“运营研究”和“组合优化问题”。
在优化问题的研究中众所周知,找到问题的最优解决方案几乎总是在计算上不可行,因为问题变得越来越大,正如您自己发现的那样。但是,通常情况下,找到保证在最佳解决方案的某个百分比内的解决方案是可行的。您应该专注于寻找近似解决方案。毕竟,您的销售目标已经是有根据的猜测,因此找到最佳解决方案已经不可能了;你还没有完整的信息。)
我要做的是首先阅读背包问题上的维基百科页面:
http://en.wikipedia.org/wiki/Knapsack_problem
这就是“我有一大堆不同价值和不同重量的物品的问题,我可以携带50磅的背包,在达到我的体重目标时,我能携带的最大值是多少?”
这不完全是你的问题,但显然它是相关的 - 你有一定数量的“价值”来最大化,并且有限数量的插槽可以将这个价值包装进去。如果您可以开始了解人们如何找到背包问题的近乎最佳解决方案,您可以将其应用于您的具体问题。
答案 1 :(得分:5)
您可以在生成排列后立即处理排列,而不是先将它们全部收集在列表中:
public delegate void Processor(List<int> args);
private void CalculateCombinations(int modes, int workDays, string combinationValues, Processor processor)
{
if (modes == 1)
{
List<int> _tempList = new List<int>();
combinationValues += Convert.ToString(workDays);
string[] _combinations = combinationValues.Split(',');
foreach (string _number in _combinations)
{
_tempList.Add(Convert.ToInt32(_number));
}
processor.Invoke(_tempList);
}
else
{
for (int i = workDays + 1; --i >= 0; )
{
CalculateCombinations(modes - 1, workDays - i, combinationValues + i + ",", processor);
}
}
}
我在这里假设,你当前的工作模式是一致的
CalculateCombinations(initial_value_1, initial_value_2, initial_value_3);
foreach( List<int> list in _modeIterations ) {
... process the list ...
}
使用直接处理方法,这将是
private void ProcessPermutation(List<int> args)
{
... process ...
}
......其他地方......
CalculateCombinations(initial_value_1, initial_value_2, initial_value_3, ProcessPermutation);
我还建议您尝试尽早修剪搜索树;如果你已经可以告诉我,参数的某些组合永远不会产生可以处理的东西,你应该在生成期间捕获那些,并且如果可能的话,完全避免递归。
在新版本的C#中,使用迭代器(?)函数生成组合可能可用于保留代码的原始结构。我还没有真正使用过这个功能(yield
),所以我不能评论它。
答案 2 :(得分:2)
问题更多在于代码本身的蛮力方法。蛮力可能是解决问题的唯一方法,但我对此表示怀疑。例如,国际象棋是Brute Force无法解决的,但计算机使用启发式方法很好地利用它来放弃不那么有前途的方法并专注于好的方法。也许你应该采取类似的方法。
另一方面,我们需要知道如何评估每个“模式”以建议任何启发式方法。在你的代码中,你只计算所有可能的组合,无论如何,如果模式达到32,它们将无法扩展...即使你将它存储在磁盘上。
答案 3 :(得分:1)
if (modes == 1)
{
List<int> _tempList = new List<int>();
combinationValues += Convert.ToString(workDays);
string[] _combinations = combinationValues.Split(',');
foreach (string _number in _combinations)
{
_tempList.Add(Convert.ToInt32(_number));
}
processor.Invoke(_tempList);
}
这段代码中的所有内容都是一遍又一遍地执行,因此该代码中的任何行都不应该使用内存而不释放它。避免内存疯狂的最明显的地方是在处理磁盘时写出combinationValues
(即使用FileStream
,而不是string
)。我认为,一般来说,按照你在这里的方式进行字符串连接是不好的,因为每个连接都会导致内存悲伤。至少使用一个stringbuilder(参见back to basics,它讨论了与C相同的问题)。但是,可能还有其他地方存在问题。找出导致内存不足错误的最简单方法可能是使用memory profiler(来自download.microsoft.com的Download Link)。
顺便说一句,我对这样的代码的倾向是拥有一个List
的全局Clear()
对象,而不是一次又一次地创建一个临时对象。
答案 4 :(得分:0)
我会用我自己的类替换List对象,该类使用预分配的数组来保存整数。我现在对此并不是很确定,但我相信List中的每个整数都是盒装的,这意味着使用的内存比使用简单的int数组要多得多。
修改:另一方面,我似乎错了:Which one is more efficient : List<int> or int[]