Initial commit
This commit is contained in:
@@ -0,0 +1,246 @@
|
||||
#include "day_manager.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
#include <algorithm>
|
||||
|
||||
DayManager::DayManager(const QTime &begin, const QTime &end) : DayManager() {
|
||||
addAvailableSlot(begin, end);
|
||||
}
|
||||
|
||||
void DayManager::addAvailableSlot(const QTime &begin, const QTime &end) {
|
||||
if (!begin.isValid() || !end.isValid() || begin >= end) {
|
||||
qWarning() << "DayManager::addAvailableSlot: begin or end is invalid";
|
||||
return;
|
||||
}
|
||||
if (begin.msecsTo(end) >= minDurationMs_) {
|
||||
availableSlots_.insert(begin, end);
|
||||
}
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserve(const QTime &duration) {
|
||||
if (!duration.isValid()) {
|
||||
qWarning() << "DayManager::reserve: duration is invalid";
|
||||
return {};
|
||||
}
|
||||
|
||||
const qint64 requiredMs = duration.msecsSinceStartOfDay();
|
||||
if (requiredMs <= 0) {
|
||||
qWarning() << "DayManager::reserve: duration is invalid";
|
||||
return {};
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> result = reserveImpl(requiredMs);
|
||||
if (!isAvailable(result.first, result.second)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
++createdRecords_;
|
||||
return result;
|
||||
}
|
||||
|
||||
template <typename... Times>
|
||||
bool DayManager::isAvailable(const QTime &first, const Times &...others) const {
|
||||
static const auto isAvailable = [](const QTime &time) {
|
||||
return time.isValid();
|
||||
};
|
||||
return isAvailable(first) && isAvailable(others...);
|
||||
}
|
||||
|
||||
QList<std::reference_wrapper<const QTime>>
|
||||
DayManager::availableSlots(qint64 requiredMs) const {
|
||||
QList<std::reference_wrapper<const QTime>> result;
|
||||
for (auto const &[begin, end] : availableSlots_.asKeyValueRange()) {
|
||||
if (begin.msecsTo(end) >= requiredMs) {
|
||||
result.append(begin);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveAvailableSlotLeft(QTime begin,
|
||||
qint64 requiredMs) {
|
||||
QTime end = availableSlots_.value(begin);
|
||||
|
||||
QTime reservedEnd = begin.addMSecs(requiredMs);
|
||||
if (reservedEnd < end) {
|
||||
addAvailableSlot(reservedEnd, end);
|
||||
}
|
||||
availableSlots_.remove(begin);
|
||||
|
||||
return {begin, reservedEnd};
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveAvailableSlotRight(QTime begin,
|
||||
qint64 requiredMs) {
|
||||
QTime end = availableSlots_.value(begin);
|
||||
availableSlots_.remove(begin);
|
||||
|
||||
QTime reservedBegin = end.addMSecs(-requiredMs);
|
||||
if (reservedBegin > begin) {
|
||||
addAvailableSlot(begin, reservedBegin);
|
||||
}
|
||||
|
||||
return {reservedBegin, end};
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveAvailableSlotRandom(QTime begin,
|
||||
qint64 requiredMs) {
|
||||
QTime end = availableSlots_.value(begin);
|
||||
if (!end.isValid()) {
|
||||
qCritical() << "DayManager::reserveAvailableSlotRandom: end is invalid";
|
||||
}
|
||||
|
||||
qint64 beginMs = begin.msecsSinceStartOfDay();
|
||||
qint64 endMs = end.msecsSinceStartOfDay();
|
||||
|
||||
// x <= bounded(x, y) < y
|
||||
qint64 highestMs = endMs - requiredMs;
|
||||
qint64 startTimeMs =
|
||||
QRandomGenerator::global()->bounded(beginMs, highestMs + 1);
|
||||
|
||||
if (startTimeMs == beginMs) {
|
||||
return reserveAvailableSlotLeft(begin, requiredMs);
|
||||
}
|
||||
if (startTimeMs == highestMs) {
|
||||
return reserveAvailableSlotRight(begin, requiredMs);
|
||||
}
|
||||
|
||||
availableSlots_.remove(begin);
|
||||
|
||||
QTime reservedBegin = QTime::fromMSecsSinceStartOfDay(startTimeMs);
|
||||
QTime reservedEnd = QTime::fromMSecsSinceStartOfDay(startTimeMs + requiredMs);
|
||||
|
||||
addAvailableSlot(begin, reservedBegin);
|
||||
addAvailableSlot(reservedEnd, end);
|
||||
|
||||
return {reservedBegin, reservedEnd};
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveAvailableSlot(QTime begin,
|
||||
qint64 requiredMs) {
|
||||
switch (startTimeMode_) {
|
||||
case StartTimeMode::Left:
|
||||
return reserveAvailableSlotLeft(begin, requiredMs);
|
||||
case StartTimeMode::Random:
|
||||
return reserveAvailableSlotRandom(begin, requiredMs);
|
||||
case StartTimeMode::Right:
|
||||
return reserveAvailableSlotRight(begin, requiredMs);
|
||||
default:
|
||||
qWarning() << "DayManager::reserve: unknown start time mode";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveImplMaximal(qint64 requiredMs) {
|
||||
auto keys = availableSlots(requiredMs);
|
||||
if (keys.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QTime begin = *std::max_element(
|
||||
keys.begin(), keys.end(), [this](const auto &largest, const auto &other) {
|
||||
return duration(largest) < duration(other);
|
||||
});
|
||||
return reserveAvailableSlot(begin, requiredMs);
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveImplMinimal(qint64 requiredMs) {
|
||||
auto keys = availableSlots(requiredMs);
|
||||
if (keys.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QTime begin =
|
||||
*std::min_element(keys.begin(), keys.end(),
|
||||
[this](const QTime &smallest, const QTime &other) {
|
||||
return duration(smallest) < duration(other);
|
||||
});
|
||||
return reserveAvailableSlot(begin, requiredMs);
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveImplQuick(qint64 requiredMs) {
|
||||
for (auto const &[begin, end] : availableSlots_.asKeyValueRange()) {
|
||||
if (begin.msecsTo(end) >= requiredMs) {
|
||||
return reserveAvailableSlot(begin, requiredMs);
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveImplRandom(qint64 requiredMs) {
|
||||
auto keys = availableSlots(requiredMs);
|
||||
if (keys.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QRandomGenerator *g = QRandomGenerator::global();
|
||||
QTime begin = keys.at(g->bounded(keys.size()));
|
||||
return reserveAvailableSlot(begin, requiredMs);
|
||||
}
|
||||
|
||||
QPair<QTime, QTime> DayManager::reserveImpl(qint64 requiredMs) {
|
||||
switch (slotSelectionMode_) {
|
||||
case SlotSelectionMode::Quick:
|
||||
return DayManager::reserveImplQuick(requiredMs);
|
||||
case SlotSelectionMode::Maximal:
|
||||
return DayManager::reserveImplMaximal(requiredMs);
|
||||
case SlotSelectionMode::Minimal:
|
||||
return DayManager::reserveImplMinimal(requiredMs);
|
||||
case SlotSelectionMode::Random:
|
||||
return DayManager::reserveImplRandom(requiredMs);
|
||||
default:
|
||||
qWarning() << "DayManager::reserve: unknown reserve mode";
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void DayManager::setMinDuration(const QTime &duration) {
|
||||
if (!duration.isValid()) {
|
||||
qWarning() << "DayManager::setMinDuration: duration is invalid";
|
||||
return;
|
||||
}
|
||||
|
||||
minDurationMs_ = duration.msecsSinceStartOfDay();
|
||||
}
|
||||
|
||||
qsizetype DayManager::calculatePossibleReservations(qint64 requiredMs) const {
|
||||
auto keys = availableSlots(requiredMs);
|
||||
return std::accumulate(keys.begin(), keys.end(), 0,
|
||||
[this, requiredMs](qsizetype sum, const QTime &key) {
|
||||
qint64 durationMs =
|
||||
duration(key).msecsSinceStartOfDay();
|
||||
return sum + std::floor(durationMs / requiredMs);
|
||||
});
|
||||
}
|
||||
|
||||
QTime DayManager::duration() const {
|
||||
if (availableSlots_.isEmpty()) {
|
||||
// qWarning() << "DayManager::duration: no available slots";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &kvRange = availableSlots_.asKeyValueRange();
|
||||
auto f = [this](qint64 sum, const auto &kv) -> qint64 {
|
||||
auto const &[begin, end] = kv;
|
||||
return sum + begin.msecsTo(end);
|
||||
};
|
||||
qint64 sum = std::accumulate(kvRange.begin(), kvRange.end(), 0, f);
|
||||
|
||||
return QTime::fromMSecsSinceStartOfDay(sum);
|
||||
}
|
||||
|
||||
QTime DayManager::duration(const QTime &begin) const {
|
||||
if (!begin.isValid()) {
|
||||
qWarning() << "DayManager::duration: begin is invalid";
|
||||
return {};
|
||||
}
|
||||
|
||||
const QTime &end = availableSlots_.value(begin);
|
||||
if (!end.isValid()) {
|
||||
qWarning() << "DayManager::duration: end is invalid";
|
||||
return {};
|
||||
}
|
||||
|
||||
return end.addMSecs(-begin.msecsSinceStartOfDay());
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
#ifndef DAY_MANAGER_H
|
||||
#define DAY_MANAGER_H
|
||||
|
||||
#include <QTime>
|
||||
|
||||
class DayManager {
|
||||
public:
|
||||
enum class SlotSelectionMode { Maximal, Minimal, Quick, Random };
|
||||
enum class StartTimeMode { Left, Random, Right };
|
||||
|
||||
private:
|
||||
QMap<QTime, QTime> availableSlots_;
|
||||
SlotSelectionMode slotSelectionMode_;
|
||||
StartTimeMode startTimeMode_;
|
||||
qint64 minDurationMs_;
|
||||
qsizetype createdRecords_;
|
||||
|
||||
public:
|
||||
inline DayManager();
|
||||
explicit DayManager(const QTime &begin, const QTime &end);
|
||||
|
||||
QPair<QTime, QTime> reserve(const QTime &duration);
|
||||
QTime duration() const;
|
||||
QTime duration(const QTime &begin) const;
|
||||
inline QTime minDuration() const noexcept;
|
||||
inline qsizetype createdRecords() const noexcept;
|
||||
inline qsizetype slotCount() const;
|
||||
inline void setMode(SlotSelectionMode mode);
|
||||
inline void setMode(StartTimeMode mode);
|
||||
void addAvailableSlot(const QTime &begin, const QTime &end);
|
||||
void setMinDuration(const QTime &duration);
|
||||
|
||||
qsizetype calculatePossibleReservations(qint64 requiredMs) const;
|
||||
|
||||
private:
|
||||
template <typename... Times>
|
||||
bool isAvailable(const QTime &first, const Times &...others) const;
|
||||
|
||||
QList<std::reference_wrapper<const QTime>>
|
||||
availableSlots(qint64 requiredMs) const;
|
||||
|
||||
QPair<QTime, QTime> reserveAvailableSlot(QTime key, qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveAvailableSlotLeft(QTime key, qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveAvailableSlotRandom(QTime key, qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveAvailableSlotRight(QTime key, qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveImpl(qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveImplMaximal(qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveImplMinimal(qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveImplQuick(qint64 requiredMs);
|
||||
QPair<QTime, QTime> reserveImplRandom(qint64 requiredMs);
|
||||
|
||||
private:
|
||||
friend class DayManagerTest;
|
||||
};
|
||||
|
||||
DayManager::DayManager()
|
||||
: slotSelectionMode_(SlotSelectionMode::Minimal),
|
||||
startTimeMode_(StartTimeMode::Left), minDurationMs_(1),
|
||||
createdRecords_(0) {}
|
||||
|
||||
void DayManager::setMode(SlotSelectionMode mode) { slotSelectionMode_ = mode; }
|
||||
|
||||
void DayManager::setMode(StartTimeMode mode) { startTimeMode_ = mode; }
|
||||
|
||||
inline QTime DayManager::minDuration() const noexcept {
|
||||
return QTime::fromMSecsSinceStartOfDay(minDurationMs_);
|
||||
}
|
||||
|
||||
qsizetype DayManager::createdRecords() const noexcept {
|
||||
return createdRecords_;
|
||||
}
|
||||
|
||||
qsizetype DayManager::slotCount() const { return availableSlots_.size(); }
|
||||
|
||||
#endif // DAY_MANAGER_H
|
||||
@@ -0,0 +1,131 @@
|
||||
#include "reservation_manager.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
|
||||
ReservationManager::ReservationManager(qsizetype dayRange, qsizetype dayBound,
|
||||
const DayManager &reference)
|
||||
: createdRecords_(0), dayBound_(dayBound), dayRange_(dayRange),
|
||||
excludedDays_(0), lastIndex_(-1), dayReference_(reference) {
|
||||
prepareDaysRange();
|
||||
}
|
||||
|
||||
void ReservationManager::setReference(const DayManager &reference) {
|
||||
dayReference_ = reference;
|
||||
days_.clear();
|
||||
lastIndex_ = -1;
|
||||
prepareDaysRange();
|
||||
}
|
||||
|
||||
qsizetype ReservationManager::calculateAvailableDaysCount() const {
|
||||
qsizetype bound =
|
||||
dayBound_ != -1 ? dayBound_ : std::numeric_limits<qsizetype>::max();
|
||||
return bound - excludedDays_;
|
||||
}
|
||||
|
||||
qsizetype
|
||||
ReservationManager::calculatePossibleReservations(qint64 requiredMs) const {
|
||||
qsizetype possibleReservations = std::accumulate(
|
||||
days_.cbegin(), days_.cend(), 0,
|
||||
[&](qsizetype sum, const DayManager manager) {
|
||||
return sum + manager.calculatePossibleReservations(requiredMs);
|
||||
});
|
||||
|
||||
qsizetype possibleReservationsByReference =
|
||||
dayReference_.calculatePossibleReservations(requiredMs);
|
||||
qsizetype futureReservationsCount =
|
||||
calculateAvailableDaysCount() - days_.size();
|
||||
|
||||
return possibleReservations +
|
||||
possibleReservationsByReference * futureReservationsCount;
|
||||
}
|
||||
|
||||
qsizetype ReservationManager::calculateAvailableSlotsCount() const {
|
||||
return std::accumulate(days_.cbegin(), days_.cend(), 0,
|
||||
[&](qsizetype sum, const DayManager manager) {
|
||||
return sum + manager.slotCount();
|
||||
});
|
||||
}
|
||||
|
||||
QPair<QDateTime, QTime> ReservationManager::reserve(const QTime &duration) {
|
||||
if (!duration.isValid()) {
|
||||
qWarning() << "ReservationManager::reserve: duration is invalid";
|
||||
return {};
|
||||
}
|
||||
|
||||
const qint64 requiredMs = duration.msecsSinceStartOfDay();
|
||||
if (requiredMs <= 0) {
|
||||
qWarning()
|
||||
<< "ReservationManager::reserve: duration should be greater than 0";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (days_.isEmpty()) {
|
||||
qWarning() << "ReservationManager::reserve: no available days";
|
||||
return {};
|
||||
}
|
||||
|
||||
std::mt19937 g{std::random_device{}()};
|
||||
QList<qsizetype> dayIndexes = days_.keys();
|
||||
std::shuffle(dayIndexes.begin(), dayIndexes.end(), g);
|
||||
|
||||
for (qsizetype dayIndex : dayIndexes) {
|
||||
QPair<QDateTime, QTime> result = reserve(dayIndex, duration);
|
||||
const auto &[beginDateTime, endTime] = result;
|
||||
if (beginDateTime.isValid() && endTime.isValid()) {
|
||||
++createdRecords_;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// qWarning() << "ReservationManager::reserve: reservation failed";
|
||||
return {};
|
||||
}
|
||||
|
||||
DayManager &ReservationManager::dayManager(qsizetype dayIndex) {
|
||||
auto it = days_.find(dayIndex);
|
||||
if (it != days_.end()) {
|
||||
return it.value();
|
||||
}
|
||||
qCritical() << "ReservationManager::dayManager: dayIndex is out of range";
|
||||
throw std::out_of_range("dayIndex is out of range");
|
||||
}
|
||||
|
||||
QPair<QDateTime, QTime> ReservationManager::reserve(qsizetype dayIndex,
|
||||
const QTime &duration) {
|
||||
|
||||
QTime beginTime, endTime;
|
||||
DayManager &dayManagerValue = dayManager(dayIndex);
|
||||
|
||||
std::tie(beginTime, endTime) = dayManagerValue.reserve(duration);
|
||||
|
||||
if (!dayManagerValue.duration().isValid()) {
|
||||
days_.remove(dayIndex);
|
||||
++excludedDays_;
|
||||
}
|
||||
prepareDaysRange(); // Add new days after excluded days
|
||||
|
||||
QDate beginDate = QDateTime::fromMSecsSinceEpoch(0).date().addDays(dayIndex);
|
||||
QDateTime beginDateTime(beginDate, beginTime);
|
||||
|
||||
return {std::move(beginDateTime), std::move(endTime)};
|
||||
}
|
||||
|
||||
void ReservationManager::prepareDaysRange() {
|
||||
if (!dayReference_.duration().isValid()) {
|
||||
qWarning() << "ReservationManager::prepareDaysRange: reference is invalid";
|
||||
return;
|
||||
}
|
||||
|
||||
auto condition = [this](qsizetype currentSize) {
|
||||
qsizetype bound =
|
||||
dayBound_ != -1 ? dayBound_ : std::numeric_limits<qsizetype>::max();
|
||||
if (lastIndex_ + 1 < bound) {
|
||||
return currentSize < dayRange_;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
for (qsizetype size = days_.size(); condition(size); ++size) {
|
||||
days_[++lastIndex_] = DayManager(dayReference_);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
#ifndef RESERVATION_MANAGER_H
|
||||
#define RESERVATION_MANAGER_H
|
||||
|
||||
#include "day_manager.h"
|
||||
|
||||
class ReservationManager {
|
||||
private:
|
||||
DayManager dayReference_;
|
||||
QMap<qsizetype /* index */, DayManager> days_;
|
||||
qsizetype createdRecords_;
|
||||
qsizetype dayBound_;
|
||||
qsizetype dayRange_;
|
||||
qsizetype excludedDays_;
|
||||
qsizetype lastIndex_;
|
||||
|
||||
public:
|
||||
inline ReservationManager() noexcept;
|
||||
explicit ReservationManager(qsizetype dayRange, qsizetype dayBound,
|
||||
const DayManager &reference);
|
||||
|
||||
inline DayManager reference() const noexcept;
|
||||
inline qsizetype createdRecords() const noexcept;
|
||||
inline void setDayBound(qsizetype dayBound);
|
||||
inline void setDayRange(qsizetype dayRange);
|
||||
void setReference(const DayManager &manager);
|
||||
|
||||
qsizetype calculateAvailableDaysCount() const;
|
||||
qsizetype calculatePossibleReservations(qint64 requiredMs) const;
|
||||
qsizetype calculateAvailableSlotsCount() const;
|
||||
|
||||
QPair<QDateTime, QTime> reserve(const QTime &duration);
|
||||
|
||||
private:
|
||||
DayManager &dayManager(qsizetype dayIndex);
|
||||
QPair<QDateTime, QTime> reserve(qsizetype dayIndex, const QTime &duration);
|
||||
void prepareDaysRange();
|
||||
|
||||
private:
|
||||
friend class ReservationManagerTest;
|
||||
};
|
||||
|
||||
ReservationManager::ReservationManager() noexcept
|
||||
: createdRecords_(0), dayBound_(-1), excludedDays_(0), lastIndex_(-1),
|
||||
dayReference_(DayManager()) {}
|
||||
|
||||
DayManager ReservationManager::reference() const noexcept {
|
||||
return dayReference_;
|
||||
}
|
||||
|
||||
inline qsizetype ReservationManager::createdRecords() const noexcept {
|
||||
return createdRecords_;
|
||||
}
|
||||
|
||||
void ReservationManager::setDayBound(qsizetype dayBound) {
|
||||
dayBound_ = dayBound;
|
||||
}
|
||||
|
||||
void ReservationManager::setDayRange(qsizetype dayRange) {
|
||||
dayRange_ = dayRange;
|
||||
}
|
||||
|
||||
#endif // RESERVATION_MANAGER_H
|
||||
Reference in New Issue
Block a user