项目排程问题

假设你是Boffin的PM,目前正同时管理A,B和C三个项目,这3个小牧都需要先有翻译部门完成翻译后, 再交至DTP部门进行排版,如果翻译部门及DTP部门的全部产能安排在单个项目上,各个项目的翻译及 版权所需工期如下表。请规划三个项目处理次序,使3个项目的总工期最短(即,第一个项目翻译开始 和最后一个项目DTP结束的时间最短)

image

要找出最短工时组合我们需要解决两个问题:

  1. 自由组合所有项目
  2. 计算每种组合所需的工时


自由组合所有项目

假设有数组[‘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个工序的项目,该如何计算排程用时。

posted @ 2020-04-16 16:10  $("#阿飞")  阅读(444)  评论(0编辑  收藏  举报