我被束之高阁,没有任何想法。
我有一个:
QList<QPair<QTime,QTime>> data;
我的QPair代表“初始时间”,“结束时间”基本上是安排某事的时间范围。
我有此Qlist,以便了解我在特定日期的时间表。
我需要知道什么是空闲时间,而我对如何做到这一点的想法已无所用。 我首先制作一个Qlist,以便按照时间表将所有内容放在同一位置,并命令该Qlist。
答案 0 :(得分:2)
首先,QPair
不是一个非常具有描述性的类型。拥有自己的结构很有意义:
// https://github.com/KubaO/stackoverflown/tree/master/questions/schedule-gap-54294739
#include <QtCore>
#include <type_traits>
struct Block {
QTime start, end;
enum class Kind { Null, Available, Busy } kind = Kind::Null;
Block() = default;
Block(const Block &) = default;
Block(const QTime &start, const QTime &end, Block::Kind kind)
: start(start), end(end), kind(kind) {
Q_ASSERT(start <= end);
}
};
由于所有操作最简单地针对单个事件执行,而不是对成对的块进行操作,因此我们也可以表示单个事件。该事件指示可能可用或繁忙的时间段的开始或结束。可用性或繁忙度是分开考虑的。 available
成员指示可用性块开始(1)或结束(-1)。 busy
成员类似地指示繁忙时段开始(1)或结束(-1)。
class Event {
public:
int available = 0, busy = 0;
QTime time;
enum class Kind { Null, BeginAvailable, EndAvailable, BeginBusy, EndBusy };
Event() = default;
Event(const QTime &time, Kind kind)
: available(kind == Kind::BeginAvailable ? +1
: kind == Kind::EndAvailable ? -1 : 0),
busy(kind == Kind::BeginBusy ? +1 : kind == Kind::EndBusy ? -1 : 0),
time(time) {}
Block::Kind blockKind() const {
return available ? Block::Kind::Available
: busy ? Block::Kind::Busy : Block::Kind::Null;
}
};
然后,您将要根据块的开始时间对其进行排序,加入重叠的块,然后根据所需的操作合并它们。您要从可用时间中减去繁忙时间,因此所需的输出为“ AvailableNotBusy”:该时间段必须既是原始可用的,也不与繁忙时间重叠。
using Blocks = QVector<Block>;
using Events = QVector<Event>;
Events eventsFromBlocks(const Blocks &);
Events sortedEvents(const Events &);
enum class MergeOp { Available, Busy, AvailableNotBusy, BusyNotAvailable };
Events mergeEvents(const Events &, MergeOp);
Blocks blocksFromEvents(const Events &);
Blocks mergeBlocks(const Blocks &a, const Blocks &b, MergeOp op) {
auto events = eventsFromBlocks(a);
events.append(eventsFromBlocks(b));
events = sortedEvents(std::move(events));
events = mergeEvents(std::move(events), op);
return blocksFromEvents(std::move(events));
}
Schedule sortSchedule(const Schedule &);
Schedule joinOverlapping(const Schedule &);
Schedule subtract(const Schedule &, const Schedule &);
例如,要获得免费计划,您需要所有可用且不忙的时间段:
Blocks freeSchedule(const Blocks &a, const Blocks &b) {
return mergeBlocks(a, b, MergeOp::AvailableNotBusy);
}
Blocks freeWorkSchedule(const Blocks &busy) {
return freeSchedule({{{8, 0}, {17, 0}, Block::Kind::Available}}, busy);
}
块和事件之间的转换相当机械化:
Events eventsFromBlocks(const Blocks &schedule) {
Events events;
events.reserve(schedule.size() * 2);
for (auto &block : schedule) {
if (block.kind == Block::Kind::Available) {
events.push_back({block.start, Event::Kind::BeginAvailable});
events.push_back({block.end, Event::Kind::EndAvailable});
} else if (block.kind == Block::Kind::Busy) {
events.push_back({block.start, Event::Kind::BeginBusy});
events.push_back({block.end, Event::Kind::EndBusy});
}
}
return events;
}
Blocks blocksFromEvents(const Events &events) {
Blocks blocks;
blocks.reserve(events.size() / 2);
bool start = true;
for (auto &event : events) {
if (start) {
blocks.push_back({event.time, {}, event.blockKind()});
} else {
blocks.back().end = event.time;
Q_ASSERT(blocks.back().kind == event.blockKind());
}
start = !start;
}
return blocks;
}
事件按时间排序:
Events sortedEvents(const Events &events) {
Events sorted = events;
std::sort(sorted.begin(), sorted.end(),
[](const Event &a, const Event &b) { return a.time < b.time; });
return sorted;
}
现在,要合并事件,我们将按时间顺序进行迭代,同时跟踪我们是否处于可用时间段和/或繁忙时间段中的任何时间。这分别由运行总和available
和busy
的非零值表示。这些和的值指示在任何给定时间有多少给定类型的块重叠。例如。 busy==3
表示我们在3个重叠的繁忙街区之内。决定我们得到什么输出的运算采用当前运行总和的值。每当操作的结果在经过某个时间点时发生更改时,结果就会作为合并事件转储。重叠事件时间的处理方法是仅在离开某个时间点后寻找operation
结果的变化。事件的recessiveKind
是我们开始时使用的默认事件类型。不同于该事件类型的操作结果的第一个更改将导致发出第一个事件。
注意:此代码中可能存在错误:)
template <typename Op>
std::enable_if_t<std::is_invocable_r_v<Event::Kind, Op, int, int>, Events> mergeEvents(
const Events &events, Event::Kind recessiveKind, Op operation) {
Events merged;
QTime prevTime;
Event::Kind prevState = recessiveKind;
int available = 0, busy = 0;
for (auto ev = events.begin();; ev++) {
if (ev != events.end()) {
available += ev->available;
busy += ev->busy;
}
Q_ASSERT(available >= 0);
Q_ASSERT(busy >= 0);
if (ev == events.end() || (ev != events.begin() && prevTime != ev->time)) {
Event::Kind state = operation(available, busy);
if (prevState != state) {
merged.push_back({ev->time, state});
prevState = state;
}
prevTime = time;
}
}
return events;
}
您可能希望执行一些常见操作:
MergeOp::Available
:仅提取与可用性相关的事件,而忽略繁忙。
MergeOp::Busy
:仅提取与繁忙相关的事件,而忽略可用性。
MergeOp::AvailableNotBusy
:在(available && !busy)
状态更改时提取事件。
MergeOp::BusyNotAvailable
:在(busy && !available)
状态更改时提取事件。
Events mergeEvents(const Events &events, MergeOp op) {
switch (op) {
case MergeOp::Available:
return mergeEvents(events, Event::Kind::EndAvailable, [](int a, int) {
return a ? Event::Kind::BeginAvailable : Event::Kind::EndAvailable;
});
case MergeOp::AvailableNotBusy:
return mergeEvents(events, Event::Kind::EndAvailable, [](int a, int b) {
return (a && !b) ? Event::Kind::BeginAvailable : Event::Kind::EndAvailable;
});
case MergeOp::Busy:
return mergeEvents(events, Event::Kind::EndBusy, [](int, int b) {
return b ? Event::Kind::BeginBusy : Event::Kind::EndBusy;
});
case MergeOp::BusyNotAvailable:
return mergeEvents(events, Event::Kind::EndBusy, [](int a, int b) {
return (b && !a) ? Event::Kind::BeginBusy : Event::Kind::EndBusy;
});
}
}