/************************************************************************
MeOS - Orienteering Software
Copyright (C) 2009-2023 Melin Software HB
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License fro more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
Melin Software HB - software@melin.nu - www.melin.nu
Eksoppsvägen 16, SE-75646 UPPSALA, Sweden
************************************************************************/
#include "stdafx.h"
#include
#include
#include
#include
#include
#include
#include
#include "oEvent.h"
#include "gdioutput.h"
#include "oDataContainer.h"
#include "random.h"
#include "meos.h"
#include "meos_util.h"
#include "localizer.h"
#include "gdifonts.h"
#include "oEventDraw.h"
#include "meosexception.h"
int ClassInfo::sSortOrder=0;
DrawInfo::DrawInfo() {
changedVacancyInfo = true;
changedExtraInfo = true;
vacancyFactor = 0.05;
extraFactor = 0.1;
minVacancy = 1;
maxVacancy = 10;
baseInterval = timeConstMinute;
minClassInterval = 2*timeConstMinute;
maxClassInterval = 3*timeConstMinute;
nFields = 10;
firstStart = timeConstHour;
maxCommonControl = 3;
allowNeighbourSameCourse = true;
coursesTogether = false;
// Statistics output from optimize start order
numDistinctInit = -1;
numRunnerSameInitMax = -1;
minimalStartDepth = -1;
}
bool ClassInfo::operator <(ClassInfo &ci)
{
if (sSortOrder==0) {
return sortFactor > ci.sortFactor;
}
else if (sSortOrder == 2) {
return pc->getSortIndex() < ci.pc->getSortIndex();
}
else if (sSortOrder == 3) {
if (unique != ci.unique) {
if (ci.nRunnersGroup != nRunnersGroup)
return nRunnersGroup > ci.nRunnersGroup;
else
return unique < ci.unique;
}
else
return firstStart > &clubRunner, vector &largest)
{
size_t maxClub = 0;
for (map >::iterator it =
clubRunner.begin(); it != clubRunner.end(); ++it) {
maxClub = max(maxClub, it->second.size());
}
for (map >::iterator it =
clubRunner.begin(); it != clubRunner.end(); ++it) {
if (it->second.size() == maxClub) {
swap(largest, it->second);
clubRunner.erase(it);
return;
}
}
}
void getRange(int size, vector &p) {
p.resize(size);
for (size_t k = 0; k < p.size(); k++)
p[k] = k;
}
void drawSOFTMethod(vector &runners, bool handleBlanks) {
if (runners.empty())
return;
//Group runners per club
map > clubRunner;
for (size_t k = 0; k < runners.size(); k++) {
int clubId = runners[k] ? runners[k]->getClubId() : -1;
clubRunner[clubId].push_back(runners[k]);
}
vector< vector > runnerGroups(1);
// Find largest club
getLargestClub(clubRunner, runnerGroups[0]);
int largeSize = runnerGroups[0].size();
int ngroups = (runners.size() + largeSize - 1) / largeSize;
runnerGroups.resize(ngroups);
while (!clubRunner.empty()) {
// Find the smallest available group
unsigned small = runners.size() + 1;
int cgroup = -1;
for (size_t k = 1; k < runnerGroups.size(); k++)
if (runnerGroups[k].size() < small) {
cgroup = k;
small = runnerGroups[k].size();
}
// Add the largest remaining group to the smallest.
vector largest;
getLargestClub(clubRunner, largest);
runnerGroups[cgroup].insert(runnerGroups[cgroup].end(), largest.begin(), largest.end());
}
unsigned maxGroup = runnerGroups[0].size();
//Permute the first group
vector pg(maxGroup);
getRange(pg.size(), pg);
permute(pg);
vector pr(maxGroup);
for (unsigned k = 0; k < maxGroup; k++)
pr[k] = runnerGroups[0][pg[k]];
runnerGroups[0] = pr;
//Find the largest group
for (size_t k = 1; k < runnerGroups.size(); k++)
maxGroup = max(maxGroup, runnerGroups[k].size());
if (handleBlanks) {
//Give all groups same size (fill with 0)
for (size_t k = 1; k < runnerGroups.size(); k++)
runnerGroups[k].resize(maxGroup);
}
// Apply algorithm recursivly to groups with several clubs
for (size_t k = 1; k < runnerGroups.size(); k++)
drawSOFTMethod(runnerGroups[k], true);
// Permute the order of groups
vector p(runnerGroups.size());
getRange(p.size(), p);
permute(p);
// Write back result
int index = 0;
for (unsigned level = 0; level < maxGroup; level++) {
for (size_t k = 0; k < runnerGroups.size(); k++) {
int gi = p[k];
if (level < runnerGroups[gi].size() && (runnerGroups[gi][level] != 0 || !handleBlanks))
runners[index++] = runnerGroups[gi][level];
}
}
if (handleBlanks)
runners.resize(index);
}
void drawMeOSMethod(vector &runners) {
if (runners.empty())
return;
map> runnersPerClub;
for (pRunner r : runners)
runnersPerClub[r->getClubId()].push_back(r);
vector> sizeClub;
for (auto &rc : runnersPerClub)
sizeClub.emplace_back(rc.second.size(), rc.first);
sort(sizeClub.rbegin(), sizeClub.rend());
int targetGroupSize = max(runners.size()/20, sizeClub.front().first);
vector> groups(1);
for (auto &sc : sizeClub) {
int currentSize = groups.back().size();
int newSize = currentSize + sc.first;
if (abs(currentSize - targetGroupSize) < abs(newSize - targetGroupSize)) {
groups.emplace_back();
}
groups.back().insert(groups.back().end(), runnersPerClub[sc.second].begin(),
runnersPerClub[sc.second].end());
}
size_t nRunnerTot = runners.size();
if (groups.front().size() > (nRunnerTot + 2) / 2 && groups.size() > 1) {
// We cannot distribute without clashes -> move some to other groups to prevent tail of same club
int toMove = groups.front().size() - (nRunnerTot + 2) / 2;
for (int i = 0; i < toMove; i++) {
int dest = 1 + i % (groups.size() - 1);
groups[dest].push_back(groups.front().back());
groups.front().pop_back();
}
}
// Permute groups
size_t maxGroupSize = 0;
vector pv;
for (auto &group : groups) {
pv.clear();
for (size_t i = 0; i < group.size(); i++) {
pv.push_back(i);
}
permute(pv);
vector tg;
tg.reserve(group.size());
for (int i : pv)
tg.push_back(group[i]);
tg.swap(group);
maxGroupSize = max(maxGroupSize, group.size());
}
runners.clear();
size_t takeMaxGroupInterval;
if (groups.size() > 10)
takeMaxGroupInterval = groups.size() / 4;
else
takeMaxGroupInterval = max(2u, groups.size() - 2);
deque recentGroups;
int ix = 0;
if (maxGroupSize * 2 > nRunnerTot) {
takeMaxGroupInterval = 2;
ix = 1;
}
while (true) {
ix++;
pair currentMaxGroup(0, -1);
int otherNonEmpty = -1;
size_t nonEmptyGroups = 0;
for (size_t gx = 0; gx < groups.size(); gx++) {
if (groups[gx].empty())
continue;
nonEmptyGroups++;
if (groups[gx].size() > currentMaxGroup.first) {
if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < currentMaxGroup.first)
otherNonEmpty = currentMaxGroup.second;
currentMaxGroup.first = groups[gx].size();
currentMaxGroup.second = gx;
}
else {
if (otherNonEmpty == -1 || groups[otherNonEmpty].size() < groups[gx].size())
otherNonEmpty = gx;
}
}
if (currentMaxGroup.first == 0)
break; // Done
int groupToUse = currentMaxGroup.second;
if (ix != takeMaxGroupInterval) {
// Select some other group
for (size_t attempt = 0; attempt < groups.size() * 2; attempt++) {
int g = GetRandomNumber(groups.size());
if (!groups[g].empty() && count(recentGroups.begin(), recentGroups.end(), g) == 0) {
groupToUse = g;
break;
}
}
}
else {
ix = 0;
}
if (!recentGroups.empty()) { //Make sure to avoid duplicates of same group (if possible)
if (recentGroups.back() == groupToUse && otherNonEmpty != -1)
groupToUse = otherNonEmpty;
}
// Try to spread groups by ensuring that the same group is not used near itself
recentGroups.push_back(groupToUse);
if (recentGroups.size() > takeMaxGroupInterval || recentGroups.size() >= nonEmptyGroups)
recentGroups.pop_front();
runners.push_back(groups[groupToUse].back());
groups[groupToUse].pop_back();
}
}
bool isFree(const DrawInfo &di, vector< vector > > &StartField, int nFields,
int FirstPos, int PosInterval, ClassInfo &cInfo)
{
int Type = cInfo.unique;
int courseId = cInfo.courseId;
int nEntries = cInfo.nRunners;
bool disallowNeighbors = !di.allowNeighbourSameCourse;
// Adjust first pos to make room for extra (before first start)
if (cInfo.nExtra > 0) {
int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
while (newFirstPos < 0)
newFirstPos += PosInterval;
int extra = (FirstPos - newFirstPos) / PosInterval;
nEntries += extra;
FirstPos = newFirstPos;
}
//Check if free at all...
for (int k = 0; k < nEntries; k++) {
bool hasFree = false;
for (int f = 0; f < nFields; f++) {
size_t ix = FirstPos + k * PosInterval;
int t = StartField[f][ix].first;
if (disallowNeighbors) {
int prevT = -1, nextT = -1;
if (PosInterval > 1 && ix + 1 < StartField[f].size())
nextT = StartField[f][ix + 1].second;
if (PosInterval > 1 && ix > 0)
prevT = StartField[f][ix - 1].second;
if ((nextT > 0 && nextT == courseId) || (prevT > 0 && prevT == courseId))
return false;
}
if (t == 0)
hasFree = true;
else if (t == Type)
return false;//Type of course occupied. Cannot put it here;
}
if (!hasFree) return false;//No free start position.
}
return true;
}
bool insertStart(vector< vector< pair > > &StartField, int nFields, ClassInfo &cInfo)
{
int Type = cInfo.unique;
int courseId = cInfo.courseId;
int nEntries = cInfo.nRunners;
int FirstPos = cInfo.firstStart;
int PosInterval = cInfo.interval;
// Adjust first pos to make room for extra (before first start)
if (cInfo.nExtra > 0) {
int newFirstPos = FirstPos - cInfo.nExtra * PosInterval;
while (newFirstPos < 0)
newFirstPos += PosInterval;
int extra = (FirstPos - newFirstPos) / PosInterval;
nEntries += extra;
FirstPos = newFirstPos;
}
for (int k = 0; k < nEntries; k++) {
bool HasFree = false;
for (int f = 0; f < nFields && !HasFree; f++) {
if (StartField[f][FirstPos + k * PosInterval].first == 0) {
StartField[f][FirstPos + k * PosInterval].first = Type;
StartField[f][FirstPos + k * PosInterval].second = courseId;
HasFree = true;
}
}
if (!HasFree)
return false; //No free start position. Fail.
}
return true;
}
}
class DrawOptimAlgo {
private:
oEvent * oe;
vector Classes;
vector Runners;
int maxNRunner = 0;
int maxGroup = 0;
int maxCourse = 0;
int bestEndPos = 0;
map otherClasses;
int bestEndPosGlobal = 0;
static int optimalLayout(int interval, vector< pair > &classes) {
sort(classes.begin(), classes.end());
vector chaining(interval, 0);
for (int k = int(classes.size()) - 1; k >= 0; k--) {
int ix = 0;
// Find free position
for (int i = 1; i 0)
nr += classes[k].second;
chaining[ix] += 1 + interval * (nr - 1);
}
int last = chaining[0];
for (int i = 1; i &cInfo, int useNControls, int alteration) {
//OutputDebugString((L"Use NC:" + itow(useNControls)).c_str());
if (di.firstStart <= 0)
di.firstStart = 0;
otherClasses.clear();
cInfo.clear();
map runnerPerGroup;
map runnerPerCourse;
int nRunnersTot = 0;
for (auto c_it : Classes) {
bool drawClass = di.classes.count(c_it->getId()) > 0;
ClassInfo *cPtr = 0;
if (!drawClass) {
otherClasses[c_it->getId()] = ClassInfo(&*c_it);
cPtr = &otherClasses[c_it->getId()];
}
else
cPtr = &di.classes[c_it->getId()];
ClassInfo &ci = *cPtr;
pCourse pc = c_it->getCourse();
if (pc && useNControls < 1000) {
if (useNControls > 0 && pc->getNumControls() > 0)
ci.unique = 1000000 + pc->getIdSum(useNControls);
else
ci.unique = 10000 + pc->getId();
ci.courseId = pc->getId();
}
else
ci.unique = ci.classId;
if (!drawClass)
continue;
int nr = 0;
if (ci.startGroupId == 0)
nr = c_it->getNumRunners(true, true, true);
else {
vector cr;
oe->getRunners(c_it->getId(), 0, cr, false);
for (pRunner r : cr)
if (r->getStartGroup(true) == ci.startGroupId)
nr++;
}
if (ci.nVacant == -1 || !ci.nVacantSpecified || di.changedVacancyInfo) {
// Auto initialize
int nVacancies = int(nr * di.vacancyFactor + 0.5);
nVacancies = max(nVacancies, di.minVacancy);
nVacancies = min(nVacancies, di.maxVacancy);
nVacancies = max(nVacancies, 0);
if (di.vacancyFactor == 0)
nVacancies = 0;
ci.nVacant = nVacancies;
ci.nVacantSpecified = false;
}
if (!ci.nExtraSpecified || di.changedExtraInfo) {
// Auto initialize
ci.nExtra = max(int(nr * di.extraFactor + 0.5), 1);
if (di.extraFactor == 0)
ci.nExtra = 0;
ci.nExtraSpecified = false;
}
ci.nRunners = nr + ci.nVacant;
if (ci.nRunners > 0) {
nRunnersTot += ci.nRunners + ci.nExtra;
cInfo.push_back(ci);
runnerPerGroup[ci.unique] += ci.nRunners + ci.nExtra;
runnerPerCourse[ci.courseId] += ci.nRunners + ci.nExtra;
}
}
maxGroup = 0;
maxCourse = 0;
maxNRunner = 0;
int a = 1 + (alteration % 7);
int b = (alteration % 3);
int c = alteration % 5;
for (size_t k = 0; k < cInfo.size(); k++) {
maxNRunner = max(maxNRunner, cInfo[k].nRunners);
cInfo[k].nRunnersGroup = runnerPerGroup[cInfo[k].unique];
cInfo[k].nRunnersCourse = runnerPerCourse[cInfo[k].courseId];
maxGroup = max(maxGroup, cInfo[k].nRunnersGroup);
maxCourse = max(maxCourse, cInfo[k].nRunnersCourse);
cInfo[k].sortFactor = cInfo[k].nRunners * a + cInfo[k].nRunnersGroup * b + cInfo[k].nRunnersCourse * c;
}
/*for (size_t k = 0; k < cInfo.size(); k++) {
auto pc = oe->getClass(cInfo[k].classId);
auto c = pc->getCourse();
wstring cc;
for (int i = 0; i < 3; i++)
cc += itow(c->getControl(i)->getId()) + L",";
wstring w = pc->getName() + L"; " + cc + L"; " + itow(cInfo[k].unique) + L"; " + itow(cInfo[k].nRunners) + L"\n";
OutputDebugString(w.c_str());
}*/
di.numDistinctInit = runnerPerGroup.size();
di.numRunnerSameInitMax = maxGroup;
di.numRunnerSameCourseMax = maxCourse;
// Calculate the theoretical best end position to use.
bestEndPos = 0;
for (map::iterator it = runnerPerGroup.begin(); it != runnerPerGroup.end(); ++it) {
vector< pair > classes;
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].unique == it->first)
classes.push_back(make_pair(cInfo[k].nRunners, cInfo[k].nExtra));
}
int optTime = optimalLayout(di.minClassInterval / di.baseInterval, classes);
bestEndPos = max(optTime, bestEndPos);
}
if (nRunnersTot > 0)
bestEndPos = max(bestEndPos, nRunnersTot / di.nFields);
if (!di.allowNeighbourSameCourse)
bestEndPos = max(bestEndPos, maxCourse * 2 - 1);
else
bestEndPos = max(bestEndPos, maxCourse);
}
public:
DrawOptimAlgo(oEvent *oe) : oe(oe) {
oe->getClasses(Classes, false);
oe->getRunners(-1, -1, Runners);
}
void optimizeStartOrder(vector< vector > > &StartField, DrawInfo &di,
vector &cInfo, int useNControls, int alteration) {
if (bestEndPosGlobal == 0) {
bestEndPosGlobal = 100000;
for (int i = 1; i <= di.maxCommonControl && i<=10; i++) {
int ncc = i;
if (i >= 10)
ncc = 0;
computeBestStartDepth(di, cInfo, ncc, 0);
bestEndPosGlobal = min(bestEndPos, bestEndPosGlobal);
}
di.minimalStartDepth = bestEndPosGlobal * di.baseInterval;
}
computeBestStartDepth(di, cInfo, useNControls, alteration);
ClassInfo::sSortOrder = 0;
sort(cInfo.begin(), cInfo.end());
int maxSize = di.minClassInterval * maxNRunner;
// Special case for constant time start
if (di.baseInterval == 0) {
di.baseInterval = 1;
di.minClassInterval = 0;
}
int startGroup = 0;
// Calculate an estimated maximal class intervall
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].startGroupId != 0)
startGroup = cInfo[k].startGroupId; // Need to be constant
int quotient = maxSize / (cInfo[k].nRunners*di.baseInterval);
if (quotient*di.baseInterval > di.maxClassInterval)
quotient = di.maxClassInterval / di.baseInterval;
if (cInfo[k].nRunnersGroup >= maxGroup)
quotient = di.minClassInterval / di.baseInterval;
if (!cInfo[k].hasFixedTime)
cInfo[k].interval = quotient;
}
for (int m = 0; m < di.nFields; m++)
StartField[m].resize(3000);
int alternator = 0;
// Fill up with non-drawn classes
for (auto &it : Runners) {
if (it->isRemoved())
continue;
int st = it->getStartTime();
int relSt = st - di.firstStart;
int relPos = relSt / di.baseInterval;
if (st > 0 && relSt >= 0 && relPos < 3000 && (relSt%di.baseInterval) == 0) {
int cid = it->getClassId(true);
if (otherClasses.count(cid) == 0) {
if (startGroup == 0 || startGroup == it->getStartGroup(true))
continue;
}
pClass cls = it->getClassRef(true);
if (cls) {
if (!di.startName.empty() && cls->getStart() != di.startName)
continue;
if (cls->hasFreeStart())
continue;
}
int unique, courseId;
auto res = otherClasses.find(cid);
if (res != otherClasses.end()) {
unique = res->second.unique;
courseId = res->second.courseId;
}
else {
res = di.classes.find(cid);
if (res != di.classes.end()) {
unique = res->second.unique;
courseId = res->second.courseId;
}
else {
int unique = 12345678;
int courseId = it->getCourse(false) ? it->getCourse(false)->getId() : 0;
}
}
int k = 0;
while (true) {
if (k == StartField.size()) {
StartField.push_back(vector< pair >());
StartField.back().resize(3000);
}
if (StartField[k][relPos].first == 0) {
StartField[k][relPos].first = unique;
StartField[k][relPos].second = courseId;
break;
}
k++;
}
}
}
// Fill up classes with fixed starttime
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].hasFixedTime) {
insertStart(StartField, di.nFields, cInfo[k]);
}
}
if (di.minClassInterval == 0) {
// Set fixed start time
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].hasFixedTime)
continue;
cInfo[k].firstStart = di.firstStart;
cInfo[k].interval = 0;
}
}
else {
// Do the distribution
for (size_t k = 0; k < cInfo.size(); k++) {
if (cInfo[k].hasFixedTime)
continue;
int minPos = 1000000;
int minEndPos = 1000000;
int minInterval = cInfo[k].interval;
int startV = di.minClassInterval / di.baseInterval;
int endV = cInfo[k].interval;
if (cInfo[k].fixedInterval != 0) {
endV = startV = cInfo[k].fixedInterval;
}
for (int i = startV; i <= endV; i++) {
int startpos = alternator % max(1, (bestEndPos - cInfo[k].nRunners * i) / 3);
startpos = 0;
int ipos = startpos;
int t = 0;
while (!isFree(di, StartField, di.nFields, ipos, i, cInfo[k])) {
t++;
// Algorithm to randomize start position
// First startpos -> bestEndTime, then 0 -> startpos, then remaining
if (t < (bestEndPos - startpos))
ipos = startpos + t;
else {
ipos = t - (bestEndPos - startpos);
if (ipos >= startpos)
ipos = t;
}
}
int endPos = ipos + i * cInfo[k].nRunners;
if (endPos < minEndPos || endPos < bestEndPos) {
minEndPos = endPos;
minPos = ipos;
minInterval = i;
}
}
cInfo[k].firstStart = minPos;
cInfo[k].interval = minInterval;
cInfo[k].overShoot = max(minEndPos - bestEndPosGlobal, 0);
insertStart(StartField, di.nFields, cInfo[k]);
alternator += alteration;
}
}
}
};
void oEvent::optimizeStartOrder(vector> &outLines, DrawInfo &di, vector &cInfo)
{
if (Classes.size()==0)
return;
struct StartParam {
int nControls;
int alternator;
double badness;
int last;
StartParam() : nControls(1), alternator(1), badness(1000), last(90000000) {}
};
StartParam opt;
bool found = false;
int nCtrl = 1;//max(1, di.maxCommonControl-2);
const int maxControlDiff = di.maxCommonControl < 1000 ? di.maxCommonControl : 10;
bool checkOnlyClass = di.maxCommonControl == 1000;
DrawOptimAlgo drawOptim(this);
while (!found) {
StartParam optInner;
for (int alt = 0; alt <= 20 && !found; alt++) {
vector< vector > > startField(di.nFields);
drawOptim.optimizeStartOrder(startField, di, cInfo, nCtrl, alt);
int overShoot = 0;
int overSum = 0;
int numOver = 0;
for (size_t k=0;k0) {
numOver++;
overShoot = max (overShoot, ci.overShoot);
overSum += ci.overShoot;
}
//laststart=max(laststart, ci.firstStart+ci.nRunners*ci.interval);
}
double avgShoot = double(overSum)/cInfo.size();
double badness = overShoot==0 ? 0 : overShoot / avgShoot;
if (badnessmaxControlDiff) //We need some limit
found = true;
}
vector< vector > > startField(di.nFields);
drawOptim.optimizeStartOrder(startField, di, cInfo, opt.nControls, opt.alternator);
outLines.emplace_back(0, L"Identifierar X unika inledningar på banorna.#" + itow(di.numDistinctInit));
outLines.emplace_back(0, L"Största gruppen med samma inledning har X platser.#" + itow(di.numRunnerSameInitMax));
outLines.emplace_back(0, L"Antal löpare på vanligaste banan X.#" + itow(di.numRunnerSameCourseMax));
outLines.emplace_back(0, L"Kortast teoretiska startdjup utan krockar är X minuter.#" + itow(di.minimalStartDepth/60));
outLines.emplace_back(0, L"");
//Find last starter
int last = opt.last;
int laststart=0;
for (size_t k=0;kgetAbsTime(laststart*di.baseInterval+di.firstStart));
outLines.emplace_back(0, L"");
int nr;
int T=0;
int sum=0;
outLines.emplace_back(1, L"Antal startande per intervall (inklusive redan lottade):");
string str="";
int empty=4;
while (T <= last) {
nr=0;
for(size_t k=0;k &classes, DrawInfo &drawInfo, vector &cInfo) const {
drawInfo.firstStart = timeConstHour * 22;
drawInfo.minClassInterval = timeConstHour;
drawInfo.maxClassInterval = 1;
drawInfo.minVacancy = 10;
drawInfo.maxVacancy = 1;
drawInfo.changedExtraInfo = false;
drawInfo.changedVacancyInfo = false;
set reducedStart;
for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) {
pClass pc = oe->getClass(*it);
if (pc) {
int fs = pc->getDrawFirstStart();
int iv = pc->getDrawInterval();
if (iv > 0 && fs > 0) {
drawInfo.firstStart = min(drawInfo.firstStart, fs);
drawInfo.minClassInterval = min(drawInfo.minClassInterval, iv);
drawInfo.maxClassInterval = max(drawInfo.maxClassInterval, iv);
drawInfo.minVacancy = min(drawInfo.minVacancy, pc->getDrawVacant());
drawInfo.maxVacancy = max(drawInfo.maxVacancy, pc->getDrawVacant());
reducedStart.insert(fs%iv);
}
}
}
drawInfo.baseInterval = drawInfo.minClassInterval;
int lastStart = -1;
for (set::iterator it = reducedStart.begin(); it != reducedStart.end(); ++it) {
if (lastStart == -1)
lastStart = *it;
else {
drawInfo.baseInterval = min(drawInfo.baseInterval, *it-lastStart);
lastStart = *it;
}
}
map runnerPerGroup;
map runnerPerCourse;
cInfo.clear();
cInfo.resize(classes.size());
int i = 0;
for (set::const_iterator it = classes.begin(); it != classes.end(); ++it) {
pClass pc = oe->getClass(*it);
if (pc) {
int fs = pc->getDrawFirstStart();
int iv = pc->getDrawInterval();
if (iv <= 0)
iv = drawInfo.minClassInterval;
if (fs <= 0)
fs = drawInfo.firstStart; //Fallback
cInfo[i].pc = pc;
cInfo[i].classId = *it;
cInfo[i].courseId = pc->getCourseId();
cInfo[i].firstStart = fs;
cInfo[i].unique = pc->getCourseId();
if (cInfo[i].unique == 0)
cInfo[i].unique = pc->getId() * 10000;
cInfo[i].firstStart = (fs - drawInfo.firstStart) / drawInfo.baseInterval;
cInfo[i].interval = iv / drawInfo.baseInterval;
cInfo[i].nVacant = pc->getDrawVacant();
cInfo[i].nExtra = pc->getDrawNumReserved();
auto spec = pc->getDrawSpecification();
cInfo[i].hasFixedTime = spec.count(oClass::DrawSpecified::FixedTime) != 0;
cInfo[i].nExtraSpecified = spec.count(oClass::DrawSpecified::Extra) != 0;
cInfo[i].nVacantSpecified = spec.count(oClass::DrawSpecified::Vacant) != 0;
cInfo[i].nRunners = pc->getNumRunners(true, true, true) + cInfo[i].nVacant;
if (cInfo[i].nRunners>0) {
runnerPerGroup[cInfo[i].unique] += cInfo[i].nRunners;
runnerPerCourse[cInfo[i].courseId] += cInfo[i].nRunners;
}
drawInfo.classes[*it] = cInfo[i];
i++;
}
}
for (size_t k = 0; k spec;
spec.emplace_back(it->getId(), 0, 0, 0, 0, VacantPosition::Mixed);
drawList(spec, method, 1, drawType);
}
}
struct GroupInfo {
int firstStart = 0;
int unassigned = 0;
int ix;
vector rPerGroup;
vector vacantPerGroup;
};
namespace {
bool sameFamily(pRunner a, pRunner b) {
wstring af = a->getFamilyName();
wstring bf = b->getFamilyName();
if (af.length() == bf.length())
return af == bf;
if (af.length() > bf.length())
swap(af, bf);
vector bff;
split(bf, L" -", bff);
for (wstring &bfs : bff) {
if (bfs == af)
return true;
}
return false;
//return af.find(bf) != wstring::npos || bf.find(af) != wstring::npos;
}
template
void groupUnassignedRunners(vector &rIn,
vector, bool>> &rGroups,
int maxPerGroup, RND rnd) {
map> families;
for (pRunner &r : rIn) {
int fam = r->getDCI().getInt("Family");
if (fam != 0) {
families[fam].push_back(r);
r = nullptr;
}
}
map> clubs;
for (pRunner &r : rIn) {
if (r)
clubs[r->getClubId()].push_back(r);
}
// Merge families to clubs, if appropriate
for (auto &fam : families) {
int cid = fam.second[0]->getClubId();
if (cid != 0 && clubs.count(cid) && int(clubs[cid].size() + fam.second.size()) < maxPerGroup) {
clubs[cid].insert(clubs[cid].end(), fam.second.begin(), fam.second.end());
fam.second.clear();
}
}
vector> rawGroups;
for (auto &g : families) {
if (g.second.size() > 0) {
rawGroups.emplace_back();
rawGroups.back().swap(g.second);
}
}
for (auto &g : clubs) {
if (g.second.size() > 0) {
rawGroups.emplace_back();
rawGroups.back().swap(g.second);
}
}
shuffle(rawGroups.begin(), rawGroups.end(), rnd);
stable_sort(rawGroups.begin(), rawGroups.end(),
[](const vector &a, const vector &b) {return (a.size()/4) < (b.size()/4); });
for (auto &g : rawGroups) {
if (int(g.size()) <= maxPerGroup)
rGroups.emplace_back(g, false);
else {
int nSplit = (g.size() + maxPerGroup - 1) / maxPerGroup;
sort(g.begin(), g.end(), [](const pRunner &a, const pRunner &b) {
wstring n1 = a->getFamilyName();
wstring n2 = b->getFamilyName();
return n1 < n2;
});
vector> famGroups(1);
for (pRunner r : g) {
if (famGroups.back().empty())
famGroups.back().push_back(r);
else {
if (!sameFamily(famGroups.back().back(), r))
famGroups.emplace_back();
famGroups.back().push_back(r);
}
}
shuffle(famGroups.begin(), famGroups.end(), rnd);
stable_sort(famGroups.begin(), famGroups.end(),
[](const vector &a, const vector &b) {return (a.size() / 4) < (b.size() / 4); });
size_t nPerGroup = g.size() / nSplit + 1;
bool brk = false;
while (!famGroups.empty()) {
rGroups.emplace_back(vector(), brk);
auto &dst = rGroups.back().first;
brk = true;
while (!famGroups.empty() && dst.size() + famGroups.back().size() <= nPerGroup) {
dst.insert(dst.end(), famGroups.back().begin(), famGroups.back().end());
famGroups.pop_back();
}
}
}
}
}
void printGroups(gdioutput &gdibase, const list &Runners) {
map> rbg;
for (const oRunner &r : Runners) {
rbg[r.getStartGroup(true)].push_back(&r);
}
gdibase.dropLine();
gdibase.addString("", 0, "List groups");
gdibase.dropLine();
map ccCount;
for (auto rr : rbg) {
auto &vr = rr.second;
sort(vr.begin(), vr.end(), [](const oRunner *a, const oRunner *b) {
if (a->getClassId(true) != b->getClassId(true))
return a->getClassId(true) < b->getClassId(true);
else
return a->getClubId() < b->getClubId(); });
gdibase.dropLine();
gdibase.addString("", 1, "Group: " + itos(rr.first));
int cls = -1;
for (const oRunner *r : vr) {
if (cls != r->getClassId(true)) {
gdibase.dropLine();
gdibase.addString("", 1, r->getClass(true));
cls = r->getClassId(true);
}
++ccCount[cls];
gdibase.addString("", 0, itow(ccCount[cls]) + L": " + r->getCompleteIdentification());
}
}
}
}
void oEvent::drawListStartGroups(const vector &spec,
DrawMethod method, int pairSize, DrawType drawType,
bool limitGroupSize,
DrawInfo *diIn) {
int nParallel = -1;
if (diIn)
nParallel = diIn->nFields;
constexpr bool logOutput = false;
auto &sgMap = getStartGroups(true);
if (sgMap.empty())
throw meosException("No start group defined");
map gInfo;
vector> orderedStartGroups;
auto overlap = [](const StartGroupInfo &a, const StartGroupInfo &b) {
if (a.firstStart >= b.firstStart && a.firstStart < b.lastStart)
return true;
if (a.lastStart > b.firstStart && a.lastStart <= b.lastStart)
return true;
if (b.firstStart >= a.firstStart && b.firstStart < a.lastStart)
return true;
if (b.lastStart > a.firstStart && b.lastStart <= a.lastStart)
return true;
return false;
};
auto formatSG = [this](int id, const StartGroupInfo &a) {
wstring w = itow(id);
if (!a.name.empty())
w += L"/" + a.name;
w += L" (" + getAbsTime(a.firstStart) + makeDash(L"-") + getAbsTime(a.lastStart) + L")";
return w;
};
for (auto &sg : sgMap) {
orderedStartGroups.emplace_back(sg.second.firstStart, sg.first);
// Check overlaps
for (auto &sgOther : sgMap) {
if (sg.first == sgOther.first)
continue;
if (overlap(sg.second, sgOther.second)) {
wstring s1 = formatSG(sg.first, sg.second), s2 = formatSG(sgOther.first, sgOther.second);
throw meosException(L"Startgrupperna X och Y överlappar.#" + s1 + L"#" + s2);
}
}
}
// Order by start time
sort(orderedStartGroups.begin(), orderedStartGroups.end());
int fs = orderedStartGroups[0].first; // First start
vector> rPerGroupTotal;
map gId2Ix;
for (auto &sg : orderedStartGroups) {
gId2Ix[sg.second] = rPerGroupTotal.size();
rPerGroupTotal.emplace_back(0, sg.second);
}
for (size_t k = 0; k < spec.size(); k++) {
auto &s = spec[k];
auto &gi = gInfo[s.classID];
gi.ix = k;
gi.firstStart = fs;
gi.rPerGroup.resize(sgMap.size());
}
vector unassigned;
int total = 0;
for (auto &r : Runners) {
if (r.isRemoved() || r.isVacant())
continue;
r.tmpStartGroup = 0;
int clsId = r.getClassId(true);
auto res = gInfo.find(clsId);
if (res != gInfo.end()) {
total++;
int id = r.getStartGroup(false);
auto idRes = gId2Ix.find(id);
if (idRes != gId2Ix.end()) {
++res->second.rPerGroup[idRes->second];
++rPerGroupTotal[idRes->second].first;
}
else {
if (id > 0)
throw meosException(L"Startgrupp med id X tilldelad Y finns inte.#" + itow(id) + L"#" + r.getCompleteIdentification());
++res->second.unassigned;
unassigned.push_back(&r);
}
}
}
vector, bool>> uaGroups;
unsigned seed = (unsigned)chrono::system_clock::now().time_since_epoch().count();
auto rnd = std::default_random_engine(seed);
int maxPerGroup = Runners.size();
if (limitGroupSize)
maxPerGroup = max(int(Runners.size() / sgMap.size()) / 4, 4);
groupUnassignedRunners(unassigned, uaGroups, maxPerGroup, rnd );
int nPerGroupAvg = (total * 9) / (sgMap.size() * 10);
shuffle(rPerGroupTotal.begin(), rPerGroupTotal.end(), rnd);
// Assign to groups
while (!uaGroups.empty()) {
stable_sort(rPerGroupTotal.begin(), rPerGroupTotal.end(),
[](const pair &a, const pair &b) {return (a.first / 4) < (b.first / 4); });
// Setup map to next start group
map nextGroup;
auto getNextGroup = [&](int ix) {
if (nextGroup.empty()) {
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
int srcId = rPerGroupTotal[k].second;
for (size_t j = 0; j < orderedStartGroups.size(); j++) {
if (orderedStartGroups[j].second == srcId) {
int nextGrpId;
if (j + 1 < orderedStartGroups.size())
nextGrpId = orderedStartGroups[j + 1].second;
else
nextGrpId = orderedStartGroups[0].second;
for (size_t kk = 0; kk < rPerGroupTotal.size(); kk++) {
if (rPerGroupTotal[kk].second == nextGrpId) {
nextGroup[k] = kk;
break;
}
}
break;
}
}
}
}
return nextGroup[ix];
};
int nextGroupToUse = -1;
for (size_t k = 0; k < rPerGroupTotal.size(); k++) {
if (nextGroupToUse == -1)
nextGroupToUse = k;
int &nGroup = rPerGroupTotal[nextGroupToUse].first;
int groupId = rPerGroupTotal[nextGroupToUse].second;
int currentGroup = nextGroupToUse;
nextGroupToUse = -1;
if (logOutput) {
gdibase.dropLine();
gdibase.addString("", 1, "Group: " + itos(groupId) + " (" + itos(nGroup) + ")");
}
while (nGroup <= nPerGroupAvg && !uaGroups.empty()) {
for (pRunner ua : uaGroups.back().first) {
ua->tmpStartGroup = groupId;
auto &gi = gInfo[ua->getClassId(true)];
int j = gId2Ix[groupId];
++gi.rPerGroup[j];
nGroup++;
}
bool skip = uaGroups.back().second;
uaGroups.pop_back();
if (skip) {
nextGroupToUse = getNextGroup(currentGroup);
break; // Assign to next group (same club)
}
}
}
nPerGroupAvg++; // Ensure convergance
}
// vacantPerGroup
for (size_t k = 0; k < spec.size(); k++) {
auto &gi = gInfo[spec[k].classID];
gi.vacantPerGroup.resize(sgMap.size());
for (int j = 0; j < spec[k].vacances; j++)
++gi.vacantPerGroup[GetRandomNumber(sgMap.size())];
}
if (logOutput)
printGroups(gdibase, Runners);
map rPerGroup;
// Ensure not too many competitors in same class per groups
vector