项目排程问题
假设你是Boffin的PM,目前正同时管理A,B和C三个项目,这3个小牧都需要先有翻译部门完成翻译后, 再交至DTP部门进行排版,如果翻译部门及DTP部门的全部产能安排在单个项目上,各个项目的翻译及 版权所需工期如下表。请规划三个项目处理次序,使3个项目的总工期最短(即,第一个项目翻译开始 和最后一个项目DTP结束的时间最短)
要找出最短工时组合我们需要解决两个问题:
- 自由组合所有项目
- 计算每种组合所需的工时
自由组合所有项目
假设有数组[‘A’,’B’] 则数组中元素自由组合结果为[‘A’,’B’]和[‘B’,’A’]。
假设我们有数组[‘A’,’B’,’C’],我们可以将之看为 ’A’与数组[‘B’,’C’]的组合,’B’ 与数组[‘A’,’C’]的组合和‘C’与数组[‘A’,’B’]的组合,结果为:
[‘A’,’B’,’C’] [‘A’,’C’,’B’]
[‘B’,’A’,’C’] [‘B’,’C’,’A’]
[‘C’,’A’,’B’] [‘C’,’B’,’A’]
假设我们有数组[P1,P2,P3,…,Pn] 就可以分解为P1与数组[P2,P3,…,Pn]组合,而数组[P2,P3,…,Pn]的组合又可以看作P2与[P3,…,Pn]的组合,依次类推,我们就可以得到以下代码:
/** * 数组中的元素自由组合 * @param {*} array * * 思路 * 考虑序列 [P1,P2,P3,...,Pn] 可以分解为 P1 与 C([P2,P3,...,Pn])的结果组合,依此类推. */ function combination(array){ var newArray =new Array(); if(array.length===1) { newArray.push(array[0]); return newArray; } if(array.length===2){ newArray.push([array[0],array[1]]); newArray.push([array[1],array[0]]); return newArray; } for(var i=0;i<array.length;i++){ var copyArray = array.slice(0); var item=copyArray.splice(i,1); var nna=combination(copyArray); for(var j=0;j<nna.length;j++){ newArray.push(item.concat(nna[j])); } } return newArray; }
计算组合所需总工时
设我们有排程ABC。
在不考虑AB排版时间的情况下,需要用时应该为: 理想时间= A.翻译时间+B.翻译时间+C.翻译时间+C.排版时间。
但是在有些情况下由于AB排版所需时间太长,导致C排版需要等待。
所以 排程ABC实际用时=理想时间+C排版等待时间。
这时我们需要计算C排版等待时间。
C排版等待时间应该等于B排版总用时-C翻译时间。
B排版总用时应该等于B排版等待时间+B排版时间。
B排版等待时间应该等于A排版时间-B翻译时间。
所以就有以下代码:
/** * 排程实际时间 * 实际时间=理想时间+等待时间 */ this.js = function(data) { var lixiangTime = this.lixiangTime(data); var dengdaiTime = this.dengdaiTime(data); var obj = { key: "", lixiangTime: lixiangTime, dengdaiTime: dengdaiTime, totalTime: lixiangTime + dengdaiTime } for (var i = 0; i < data.length; i++) { obj.key += data[i].name; } return obj; }; /** * 理想时间 * 设排程ABC * 理想时间=A.翻译+B.翻译+C.翻译+C.排版 */ this.lixiangTime = function(data) { var time = 0; for (var i = 0; i < data.length; i++) { if (i === data.length - 1) return time += parseInt(data[i].paiban) + parseInt(data[i].fanyi); time += parseInt(data[i].fanyi); } }; /*** * 翻译等待时间 * 设排程ABC * B排版等待时间=A.排版时间-B.翻译时间 * C排版等待时间=B排版时间+B排版等待时间-C翻译时间 * 时间不能小于0 */ this.dengdaiTime = function(data) { var time = 0; for (var i = 1; i < data.length; i++) { time = parseInt(time) + parseInt(data[i - 1].paiban) - parseInt(data[i].fanyi); time = time < 0 ? 0 : time; } return time; };
完整代码
HTML 代码:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>项目排程</title> <link href="css/index.css" rel="stylesheet" type="text/css" /> </head> <body> <div> 假设你是Boffin的PM,目前正同时管理A,B和C三个项目,这3个小牧都需要先有翻译部门完成翻译后, 再交至DTP部门进行排版,如果翻译部门及DTP部门的全部产能安排在单个项目上,各个项目的翻译及 版权所需工期如下表。请规划三个项目处理次序,使3个项目的总工期最短(即,第一个项目翻译开始 和最后一个项目DTP结束的时间最短) </div> <div data-bind="with:newItem"> <div> <label for="projectName">项目名称:</label> <input type="text" id="projectName" data-bind="value:name" /> </div> <div> <label for="fanyi">翻译用时:</label> <input type="text" id="fanyi" data-bind="value:fanyi" /> </div> <div> <label for="paiban">排版用时:</label> <input type="text" id="paiban" data-bind="value:paiban" /> </div> <div> <button data-bind="click:$parent.addItem">新增</button> </div> </div> <div> <table> <thead> <tr> <th>项目名称</th> <th>翻译用时</th> <th>排版用时</th> <th>操作</th> </tr> </thead> <tbody data-bind="foreach: allItems"> <tr> <th><label for="" data-bind="text:name"></label></td> <td><label for="" data-bind="text:fanyi"></label></td> <td><label for="" data-bind="text:paiban"></label></td> <td><a href='#' data-bind='click: $root.removeItem'>删除</a></td> </tr> </tbody> </table> <label for="" data-bind="text:allItems().length"></label> <br> <button data-bind="click:jisuan">计算</button> </div> <div> 日志: <div id="log" data-bind="foreach:allLogs"> <span data-bind="text:$data"></span> </div> </div> <script src="js/knockout-3.5.1.js" type="text/javascript"></script> <script src="js/index.js" type="text/javascript"></script> </body> </html>
JS 代码:
var listModel = function(items) { var self = this; this.newItem = new function() { this.name = ko.observable(""); this.fanyi = ko.observable(""); this.paiban = ko.observable(""); this.toJson = function() { return JSON.parse(ko.toJSON(this)); }; this.clear = function() { this.name(""); this.fanyi(""); this.paiban(""); }; }; this.allItems = ko.observableArray(items); this.allLogs = ko.observableArray([]); this.addItem = function() { var data = this.newItem.toJson(); console.log(data); if (this.allItems.indexOf(data) < 0) { this.allItems.push(data); this.newItem.clear(); } }.bind(this); this.removeItem = function() { self.allItems.remove(this); }; /** * 自由组合所有项目 * 并在组合中找出用时最少组合 */ this.jisuan = function() { this.addLog("开始计算。。。"); var data = this.allItems(); var bestTime; data = combination(data); for (var i = 0; i < data.length; i++) { var time = this.js(data[i]); this.addLog("排列顺序:" + time.key + " 理想时间:" + time.lixiangTime + " 等待时间:" + time.dengdaiTime + " 实际时间:" + time.totalTime); if (!bestTime) bestTime = time; else if (bestTime.totalTime > time.totalTime) { bestTime = time; } } this.addLog("最优排列顺序:" + bestTime.key + " 理想时间:" + bestTime.lixiangTime + " 等待时间:" + bestTime.dengdaiTime + " 实际时间:" + bestTime.totalTime) this.addLog("开始结束。。。"); }.bind(this); this.addLog = function(mesage) { this.allLogs.push(mesage); var log = document.querySelector("#log") log.scrollTop = log.scrollHeight; }; /** * 排程实际时间 * 实际时间=理想时间+等待时间 */ this.js = function(data) { var lixiangTime = this.lixiangTime(data); var dengdaiTime = this.dengdaiTime(data); var obj = { key: "", lixiangTime: lixiangTime, dengdaiTime: dengdaiTime, totalTime: lixiangTime + dengdaiTime } for (var i = 0; i < data.length; i++) { obj.key += data[i].name; } return obj; }; /** * 理想时间 * 设排程ABC * 理想时间=A.翻译+B.翻译+C.翻译+C.排版 */ this.lixiangTime = function(data) { var time = 0; for (var i = 0; i < data.length; i++) { if (i === data.length - 1) return time += parseInt(data[i].paiban) + parseInt(data[i].fanyi); time += parseInt(data[i].fanyi); } }; /*** * 翻译等待时间 * 设排程ABC * B排版等待时间=A.排版时间-B.翻译时间 * C排版等待时间=B排版时间+B排版等待时间-C翻译时间 * 时间不能小于0 */ this.dengdaiTime = function(data) { var time = 0; for (var i = 1; i < data.length; i++) { time = parseInt(time) + parseInt(data[i - 1].paiban) - parseInt(data[i].fanyi); time = time < 0 ? 0 : time; } return time; }; } var items = [{ name: "A", fanyi: 20, paiban: 13 }, { name: "B", fanyi: 4, paiban: 10 }, { name: "C", fanyi: 13, paiban: 3 }]; ko.applyBindings(new listModel(items)); /** * 数组中的元素自由组合 * @param {*} array * * 思路 * 考虑序列 [P1,P2,P3,...,Pn] 可以分解为 P1 与 C([P2,P3,...,Pn])的结果组合,依此类推. */ function combination(array) { var newArray = new Array(); if (array.length === 1) { newArray.push(array[0]); return newArray; } if (array.length === 2) { newArray.push([array[0], array[1]]); newArray.push([array[1], array[0]]); return newArray; } for (var i = 0; i < array.length; i++) { var copyArray = array.slice(0); var time = copyArray.splice(i, 1); var nna = combination(copyArray); for (var j = 0; j < nna.length; j++) { newArray.push(time.concat(nna[j])); } } return newArray; }
css代码:
div { margin: 10px; } #log { border-style: ridge; border-width: 1px; border-color: cadetblue; min-height: 160px; max-height: 400px; overflow-y: scroll; } table { border-collapse: collapse; } table thead tr { background-color: #34495e; } table tbody tr:nth-child(odd) { background-color: #95a5a6; } table tbody tr:nth-child(even) { background-color: #7f8c8d; } table th { min-width: 80px; border-width: 1px; border-style: ridge; border-color: #130f40; } table tr td { border-width: 1px; border-style: ridge; border-color: #34495e; text-align: center; } #log span { display: block; margin: 5px; }
思考
该项目只有两个工序,倘若是N个工序的项目,该如何计算排程用时。