/// <summary> /// Push/Pull 人力 /// </summary> /// <param name="staffingStatistic">标的的SQ</param> /// <param name="required">短缺量,或过剩量有正负数</param> /// <param name="contributionHistory">记录贡献位置</param> /// <param name="insertRules">可安排的离线规则</param> /// <param name="startIndex">班表(Assignment)的起始</param> /// <param name="contributionLength">班表(Assignment)有效贡献长度</param> private void Contribute(IStaffingStatistic staffingStatistic, int required, List <List <Tuple <int, int, double> > > contributionHistory, IEnumerable <SubEventInsertRule> insertRules, int startIndex, int contributionLength) { var isOverStaff = required < 0; var times = Math.Abs(required); for (var i = 0; i < times; i++) { if (isOverStaff) { staffingStatistic.Push(startIndex, contributionLength, -1); if (contributionHistory.Count <= i) { continue; } foreach (var e in contributionHistory[i]) { staffingStatistic.Push(e.Item1, e.Item2, 1); } contributionHistory.RemoveAt(i); } else // underStaff { staffingStatistic.Push(startIndex, contributionLength, 1); contributionHistory.Add(new List <Tuple <int, int, double> >()); foreach (var rule in insertRules.Where(r => !r.SubEvent.OnService)) { //设定一个初始最大比对变量 var lowestScore = new Tuple <int, int, double>(-1, 0, int.MaxValue); // (离线事件startIndex),(离线事件长度),(离线事件在此时间短缺率) var length = rule.SubEventLength / 5; //离线事件长度 var rounds = rule.GetPossibleMovementTimes(); // 离线事件可以安排的可能位置 do { var occurIndex = startIndex + ((rule.TimeRange.StartValue + (rounds * rule.OccurScale)) / CellUnit); // 离线事件startIndex var score = staffingStatistic.GetShortfall(occurIndex, length); // 离线事件在此时间短缺率 if (score < lowestScore.Item3) // 愈小愈好 { lowestScore = new Tuple <int, int, double>(occurIndex, length, score); } rounds--; } while (rounds >= 0); staffingStatistic.Push(lowestScore.Item1, length, -1); //lowestSorce.Item1 意思是最后胜出的离线事件startIndex contributionHistory[i].Add(lowestScore); } } } }
private void NewF(IStaffingStatistic staffingStatistic) { var prioritiedAssignmentTypeGroup = new Dictionary<int, IList<string>>(10); var validAssignmentTypes = new Dictionary<string, HeaderContainer<AssignmentType, DailyCounter<AssignmentType>, DateTime>>(); foreach (var item in _assignmentTypes) { var termStyle = item.Value.Header; if (!termStyle.ServiceQueue.Equals(staffingStatistic.Entity)) continue; //优先级分组 var priority = termStyle.EstimationPriority; if (!prioritiedAssignmentTypeGroup.ContainsKey(priority)) prioritiedAssignmentTypeGroup.Add(priority, new List<string>()); prioritiedAssignmentTypeGroup[priority].Add(item.Key); validAssignmentTypes[item.Key] = item.Value; } var dCells = new double[Convert.ToInt32(_enquiryRange.GetLength().TotalMinutes) / CellUnit]; //班别分布量 var assignmentTypeCoverage = new Tuple<int, int>(int.MaxValue, int.MinValue); //整月班表贡献,分布统计(循环次数 = 天 x 班表数量) Loop((dateKey, dateIndex, isHoliday) => { foreach (var style in validAssignmentTypes) { var termStyle = style.Value.Header; if (!termStyle.WorkingDayMask.CanWork(dateKey, isHoliday)) continue; if(!style.Value.Header.Locked) style.Value[dateKey].Max = 0; // reset var sensedTermStyle = termStyle.Sense(dateKey); var assignmentStartIndex = (sensedTermStyle.TimeRange.StartValue / CellUnit) + dateIndex; var endIndex = assignmentStartIndex + (sensedTermStyle.TimeRange.Length / CellUnit); #region 求所有AssignmentType时间含盖范围 var hi = assignmentTypeCoverage.Item2; var lo = assignmentTypeCoverage.Item1; var range = sensedTermStyle.TimeRange; if (range.StartValue < lo) lo = range.StartValue; if (range.EndValue > hi) hi = range.EndValue; assignmentTypeCoverage.Item1 = lo / CellUnit; //5分钟计量 assignmentTypeCoverage.Item2 = hi / CellUnit; //5分钟计量 #endregion // 將cell補滿預設為 online = true , 大循环注意 for (var i = assignmentStartIndex; i < endIndex; i++) dCells[i] += 1; //班别分布量 #region 扣除离线事件位移产生的相交位置(不管怎排都无法贡献的格子位置,一定会造成离线) FindSubEventInsertRuleIntersections(sensedTermStyle.SubEventInsertRules, i => { dCells[i + assignmentStartIndex] -= 1;//绝对位置使用, 减去无法贡献的位置 }); #endregion } }); //开始一天一天估算 Loop((dateKey, dateIndex, isHoliday) => { var pushed = new Dictionary<AssignmentType, List<List<Luna.Core.Tuple<int, int, double>>>>(); foreach (var g in prioritiedAssignmentTypeGroup) { var typeNames = g.Value.Select(t => _assignmentTypes[t].Header).Where(t => t.WorkingDayMask.CanWork(dateKey, isHoliday)); var fixedEstimation = new Dictionary<string, AssignmentType>(); Loop(20, k => { foreach (var termStyle in typeNames) // enumerate assignment type { if(fixedEstimation.ContainsKey(termStyle.Text)) continue; var sensedTermStyle = termStyle.Sense(dateKey); var startIndex = (sensedTermStyle.TimeRange.StartValue / CellUnit) + dateIndex; var length = sensedTermStyle.TimeRange.Length / CellUnit; var endIndex = startIndex + length; var unContributionPosition = new bool[length]; var required = 0; if(termStyle.Locked) { required = (int)_assignmentTypes[termStyle.Text][dateKey].Max; fixedEstimation[termStyle.Text] = termStyle; } else { #region 算法公式 //找出离线事件永远为离线交集 FindSubEventInsertRuleIntersections(sensedTermStyle.SubEventInsertRules, i => { unContributionPosition[i] = true; //使用相对位置记录, 注意true意思为无法贡献 }); var contributableCells = new List<double>(length);// 可贡献的格子 var validContributionPositionCount = 0; var sumOfRequired = 0d; // required(班次) = Sum(Cell班次不足量) / 可贡献的Cell数 for (var i = startIndex; i < endIndex; i++) { if (unContributionPosition[i - startIndex])//相對位置 continue; // 此格无法贡献,因为有离线交集 //计算Cell班次不足量 sumOfRequired += staffingStatistic.M1(i, dCells[i]).Self(contributableCells.Add); validContributionPositionCount++; // 可贡献的Cell数量 } var avg = (sumOfRequired / validContributionPositionCount); (contributableCells.OrderBy(v => Math.Abs(v - avg)).Self(difference => { if (MathLib.StandardDeviation(difference.ToArray()) < 1.0) return; validContributionPositionCount = (int)(contributableCells.Count * 0.8); // take 80% }).Take(validContributionPositionCount).Sum() / validContributionPositionCount).Self(v => { if (v < 0) required = (int)(v / 1); else if (0 < v) required = (int)Math.Ceiling(v); }); var max = _assignmentTypes[termStyle.Text][dateKey].Max + required; _assignmentTypes[termStyle.Text][dateKey].Max = Math.Max(0, max); #endregion } if (!pushed.ContainsKey(termStyle)) pushed[termStyle] = new List<List<Tuple<int, int, double>>>(Math.Abs(required)); // index, length, score Contribute(staffingStatistic, required, pushed[termStyle], sensedTermStyle.SubEventInsertRules, startIndex, length); // 相当于 startIndex - endIndex } return staffingStatistic.GetDeviation(assignmentTypeCoverage.Item1 + dateIndex, assignmentTypeCoverage.Item2 + dateIndex, dateIndex); //resultOfScore 天离散系数 }); } SumOfDailyShiftEstimation(dateKey, dateKey.IndexOf(Schedule.Start)); }); //班次估算值发生变化 _shiftEstimatesChanged = true; NotifyOfPropertyChange(() => AssignmentTypes); NotifyOfPropertyChange(() => StatisticItems);//不可移除, 次通知需要响应给staffingChartview CellChanged = true; }
/// <summary> /// Push/Pull 人力 /// </summary> /// <param name="staffingStatistic">标的的SQ</param> /// <param name="required">短缺量,或过剩量有正负数</param> /// <param name="contributionHistory">记录贡献位置</param> /// <param name="insertRules">可安排的离线规则</param> /// <param name="startIndex">班表(Assignment)的起始</param> /// <param name="contributionLength">班表(Assignment)有效贡献长度</param> private void Contribute(IStaffingStatistic staffingStatistic, int required, List<List<Tuple<int, int, double>>> contributionHistory, IEnumerable<SubEventInsertRule> insertRules, int startIndex, int contributionLength) { var isOverStaff = required < 0; var times = Math.Abs(required); for (var i = 0; i < times; i++) { if (isOverStaff) { staffingStatistic.Push(startIndex, contributionLength, -1); if (contributionHistory.Count <= i) continue; foreach (var e in contributionHistory[i]) staffingStatistic.Push(e.Item1, e.Item2, 1); contributionHistory.RemoveAt(i); } else // underStaff { staffingStatistic.Push(startIndex, contributionLength, 1); contributionHistory.Add(new List<Tuple<int, int, double>>()); foreach (var rule in insertRules.Where(r => !r.SubEvent.OnService)) { //设定一个初始最大比对变量 var lowestScore = new Tuple<int, int, double>(-1, 0, int.MaxValue); // (离线事件startIndex),(离线事件长度),(离线事件在此时间短缺率) var length = rule.SubEventLength / 5; //离线事件长度 var rounds = rule.GetPossibleMovementTimes(); // 离线事件可以安排的可能位置 do { var occurIndex = startIndex + ((rule.TimeRange.StartValue + (rounds * rule.OccurScale)) / CellUnit); // 离线事件startIndex var score = staffingStatistic.GetShortfall(occurIndex, length); // 离线事件在此时间短缺率 if (score < lowestScore.Item3) // 愈小愈好 lowestScore = new Tuple<int, int, double>(occurIndex, length, score); rounds--; } while (rounds >= 0); staffingStatistic.Push(lowestScore.Item1, length, -1); //lowestSorce.Item1 意思是最后胜出的离线事件startIndex contributionHistory[i].Add(lowestScore); } } } }
private void NewF(IStaffingStatistic staffingStatistic) { var prioritiedAssignmentTypeGroup = new Dictionary <int, IList <string> >(10); var validAssignmentTypes = new Dictionary <string, HeaderContainer <AssignmentType, DailyCounter <AssignmentType>, DateTime> >(); foreach (var item in _assignmentTypes) { var termStyle = item.Value.Header; if (!termStyle.ServiceQueue.Equals(staffingStatistic.Entity)) { continue; } //优先级分组 var priority = termStyle.EstimationPriority; if (!prioritiedAssignmentTypeGroup.ContainsKey(priority)) { prioritiedAssignmentTypeGroup.Add(priority, new List <string>()); } prioritiedAssignmentTypeGroup[priority].Add(item.Key); validAssignmentTypes[item.Key] = item.Value; } var dCells = new double[Convert.ToInt32(_enquiryRange.GetLength().TotalMinutes) / CellUnit]; //班别分布量 var assignmentTypeCoverage = new Tuple <int, int>(int.MaxValue, int.MinValue); //整月班表贡献,分布统计(循环次数 = 天 x 班表数量) Loop((dateKey, dateIndex, isHoliday) => { foreach (var style in validAssignmentTypes) { var termStyle = style.Value.Header; if (!termStyle.WorkingDayMask.CanWork(dateKey, isHoliday)) { continue; } if (!style.Value.Header.Locked) { style.Value[dateKey].Max = 0; // reset } var sensedTermStyle = termStyle.Sense(dateKey); var assignmentStartIndex = (sensedTermStyle.TimeRange.StartValue / CellUnit) + dateIndex; var endIndex = assignmentStartIndex + (sensedTermStyle.TimeRange.Length / CellUnit); #region 求所有AssignmentType时间含盖范围 var hi = assignmentTypeCoverage.Item2; var lo = assignmentTypeCoverage.Item1; var range = sensedTermStyle.TimeRange; if (range.StartValue < lo) { lo = range.StartValue; } if (range.EndValue > hi) { hi = range.EndValue; } assignmentTypeCoverage.Item1 = lo / CellUnit; //5分钟计量 assignmentTypeCoverage.Item2 = hi / CellUnit; //5分钟计量 #endregion // 將cell補滿預設為 online = true , 大循环注意 for (var i = assignmentStartIndex; i < endIndex; i++) { dCells[i] += 1; //班别分布量 } #region 扣除离线事件位移产生的相交位置(不管怎排都无法贡献的格子位置,一定会造成离线) FindSubEventInsertRuleIntersections(sensedTermStyle.SubEventInsertRules, i => { dCells[i + assignmentStartIndex] -= 1;//绝对位置使用, 减去无法贡献的位置 }); #endregion } }); //开始一天一天估算 Loop((dateKey, dateIndex, isHoliday) => { var pushed = new Dictionary <AssignmentType, List <List <Luna.Core.Tuple <int, int, double> > > >(); foreach (var g in prioritiedAssignmentTypeGroup) { var typeNames = g.Value.Select(t => _assignmentTypes[t].Header).Where(t => t.WorkingDayMask.CanWork(dateKey, isHoliday)); var fixedEstimation = new Dictionary <string, AssignmentType>(); Loop(20, k => { foreach (var termStyle in typeNames) // enumerate assignment type { if (fixedEstimation.ContainsKey(termStyle.Text)) { continue; } var sensedTermStyle = termStyle.Sense(dateKey); var startIndex = (sensedTermStyle.TimeRange.StartValue / CellUnit) + dateIndex; var length = sensedTermStyle.TimeRange.Length / CellUnit; var endIndex = startIndex + length; var unContributionPosition = new bool[length]; var required = 0; if (termStyle.Locked) { required = (int)_assignmentTypes[termStyle.Text][dateKey].Max; fixedEstimation[termStyle.Text] = termStyle; } else { #region 算法公式 //找出离线事件永远为离线交集 FindSubEventInsertRuleIntersections(sensedTermStyle.SubEventInsertRules, i => { unContributionPosition[i] = true; //使用相对位置记录, 注意true意思为无法贡献 }); var contributableCells = new List <double>(length); // 可贡献的格子 var validContributionPositionCount = 0; var sumOfRequired = 0d; // required(班次) = Sum(Cell班次不足量) / 可贡献的Cell数 for (var i = startIndex; i < endIndex; i++) { if (unContributionPosition[i - startIndex]) //相對位置 { continue; // 此格无法贡献,因为有离线交集 } //计算Cell班次不足量 sumOfRequired += staffingStatistic.M1(i, dCells[i]).Self(contributableCells.Add); validContributionPositionCount++; // 可贡献的Cell数量 } var avg = (sumOfRequired / validContributionPositionCount); (contributableCells.OrderBy(v => Math.Abs(v - avg)).Self(difference => { if (MathLib.StandardDeviation(difference.ToArray()) < 1.0) { return; } validContributionPositionCount = (int)(contributableCells.Count * 0.8); // take 80% }).Take(validContributionPositionCount).Sum() / validContributionPositionCount).Self(v => { if (v < 0) { required = (int)(v / 1); } else if (0 < v) { required = (int)Math.Ceiling(v); } }); var max = _assignmentTypes[termStyle.Text][dateKey].Max + required; _assignmentTypes[termStyle.Text][dateKey].Max = Math.Max(0, max); #endregion } if (!pushed.ContainsKey(termStyle)) { pushed[termStyle] = new List <List <Tuple <int, int, double> > >(Math.Abs(required)); // index, length, score } Contribute(staffingStatistic, required, pushed[termStyle], sensedTermStyle.SubEventInsertRules, startIndex, length); // 相当于 startIndex - endIndex } return(staffingStatistic.GetDeviation(assignmentTypeCoverage.Item1 + dateIndex, assignmentTypeCoverage.Item2 + dateIndex, dateIndex)); //resultOfScore 天离散系数 }); } SumOfDailyShiftEstimation(dateKey, dateKey.IndexOf(Schedule.Start)); }); //班次估算值发生变化 _shiftEstimatesChanged = true; NotifyOfPropertyChange(() => AssignmentTypes); NotifyOfPropertyChange(() => StatisticItems);//不可移除, 次通知需要响应给staffingChartview CellChanged = true; }