假设我有一个顺序容器,以及该容器中当前“活动”的元素的范围(一对迭代器)。在某个时候,我计算了一个新的应该处于活动状态的元素范围,它可能与之前的范围重叠。然后,我想迭代旧活动范围内但不在新活动范围内的元素以“停用”它们(并类似地迭代新范围内但不是旧范围内的元素以“激活” ' 他们)。
这可能吗?
如果我知道新活动范围的开始在容器中总是比旧活动范围的开始晚,是否会变得更容易?
出于问题的目的,假设容器是一个向量。
您可以将两组用于最后一个活动范围,另一个用于当前活动范围。使用set_difference
算法获取要激活/停用的对象。
你需要的是一个range_difference
函数。我虽然 Boost.Range 会提供类似的东西,但我在他们的文档中没有找到任何东西(好吧,我没有彻底搜索......),所以我自己整理了。
以下函数返回一对范围,其中包含由 (first1,last1) 表示的范围与由 (first2,last2) 表示的范围之间的差异的结果。前提条件是 first1 必须位于 first2 之前或相同的位置。
template <typename InputIterator>
std::pair<
std::pair<InputIterator, InputIterator>,
std::pair<InputIterator, InputIterator> >
range_difference(InputIterator first1, InputIterator last1,
InputIterator first2, InputIterator last2)
{
typedef std::pair<InputIterator, InputIterator> Range;
InputIterator it;
// first1 must be <= first2
for (it = first1 ; it != last1 && it != first2 ; ++it);
Range left_range = std::make_pair(first1, it); // Left range
if (it == last1)
return std::make_pair(left_range, std::make_pair(first2, first2));
// it == first2
while (it != last1 && it != last2) ++it;
return std::make_pair(left_range, std::make_pair(it, last1)); // Right range
}
如果 range2 完全包含在 range1 中,则差异的结果可以由两部分组成。你最终得到一个左范围和一个右范围:
|_____________________|__________________|________________________|
first1 first2 last2 last1
在这种情况下,函数返回 (first1, first2),(last2, last1)。
在这个其他配置中,
|_____________________| |________________________|
first1 last1 first2 last2
函数返回 (first1, last1),(first2, first2)。还有许多其他可能的配置。但是,要知道的重要一点是,在正确范围为空的情况下,它将位于max(first2, last1)。您将在示例中看到这是多么必要。
最后,如果first1和first2在同一个位置,返回的左边范围为空,id(first1,first1)。
现在,我们如何使用此功能来解决您的问题?好吧,这对于“停用”范围来说相当容易,但对于“激活”范围来说有点棘手:
typedef std::vector<Activable>::iterator Iterator;
Iterator old_beg, old_end, new_beg, new_end; // Old and new ranges
typedef std::pair<Iterator, Iterator> Range;
typedef std::pair<Range, Range> SplitRange;
SplitRange deactivate = range_difference(old_beg, old_end, new_beg, new_end);
// Left range
for (Iterator it = deactivate.first.first;
it != deactivate.first.second;
++it)
it->deactivate();
// Right range
for (Iterator it = deactivate.second.first;
it != deactivate.second.second;
++it)
it->deactivate();
SplitRange activate =
range_difference(new_beg, new_end, new_beg, deactivate.second.first);
// Note the use of the previously returned right range -------^
for (Iterator it = activate.second.first;
it != activate.second.second;
++it)
it->activate();
你去吧。也许这个解决方案对你的问题有点矫枉过正,但我认为这个range_difference
功能在很多地方都很有用。
这是一个简单的解决方案:
typedef std::pair<std::vector<T>::iterator, std::vector<T>::iterator> Range;
void Update(std::vector<T>& v, Range oldActive, Range newActive)
{
int op = 0;
for (std::vector<T>::iterator i = v.begin(), end = v.end(); i != end; ++i)
{
if (i == oldActive.first) op += 1;
if (i == oldActive.second) op -= 1;
if (i == newActive.first) op += 2;
if (i == newActive.second) op -= 2;
if (op == 1) i->Deactivate();
if (op == 2) i->Activate();
}
}
这故意将简单性放在效率之前作为起点,因为它扫描整个向量;另一方面,它是单次传递,不进行复制。
我想我会保持简单:
// Iterators denoting the old and new ranges (might be cleaner to use some kind
// of typedef like James Hopkin's did, but that's not the most important)
std::vector<Activable>::iterator old_beg,
old_end,
new_beg,
new_end;
std::vector<Activable>::iterator it;
// Deactivate
for (it = old_beg ; // go from the beginning of the old range
it != old_end && it != new_beg ; // to either the end of the old one or the
++it) // beginning of the new one
it->deactivate();
// "Jump" to the correct position
if (it == old_end) it = new_beg; // no overlap
else it = old_end; // overlap
// Activate
for (; it != new_end ; ++it)
it->activate();
你会注意到我假设新范围不能完全包含在旧范围中(例如,你不能有一个从索引 4 到 10 的旧范围,而一个从 5 到 7 的新范围)。如果是这种情况,您需要稍微更改算法。