C++ std::set 使用基于时间的比较器
C++ std::set with a comparator that is time based?
在 C++std::set 中使用的比较器依赖于时间是否不好?即比较器一次返回一件事,但即使对于完全相同的对象也可能在不同的时间返回另一个结果?
我读过 std::sets 在给定比较器的情况下是弱排序的。也许有一种方法可以在获取mySet.begin()
或myset.end()
值之前刷新顺序?
作为可能随时间变化的比较器的示例,请考虑事件并希望确定更接近当前时间的事件的优先级:
class Event {
public:
int64_t nTime;
}
class EventComparator {
// pa < pb
bool operator()(Event* pa, Event* pb) {
int64_t now = GetTime();
return (std::abs(pa->nTime - now) > std::abs(pb->nTime - now));
}
}
也许有更好的方法按时间对事件进行排序?显然,我可以只做一个向量并循环找到最大的事件,但只是认为使用内置插槽作为比较器会很好。
如果std::set
中元素的相对顺序发生变化,则结果是未定义的行为。
就你而言,只要事情永远不会在未来发生,时间永远不会溢出,-now
就毫无意义。 如果在任何时候在当前时间之前和之后发生事情,您的代码将表现出未定义的行为。
如果你只是简单地按时间对所有内容进行排序,然后寻找现在附近的元素,并从那里进行处理,你会得到类似的结果,但没有未定义的行为。
即,有一张从int64_t
到Event*
的地图。 将时间戳存储在int64_t
中(这使某些事情变得更容易)。 然后获取当前时间lower_bound
,并仔细检查它周围,以找到甚至"最近"到现在(或您选择的任何窗口)。
如果您的编译器支持透明比较器(C++14 功能),则可以使用 std::set
执行此操作。 只需在Event*
和int64_t
时间戳之间实现一个透明的比较器。
弄错了,C++大师纠正我:
我相信std::set
实例使用的比较器对底层容器内集合中包含的元素进行排序。这对于最大程度地降低 std::set
定义的操作和功能的复杂性是必要的。由于集合依赖于这种排序,因此更改顺序是非常不明智的 - 它会导致间歇性(有时顺序可能仍然是正确的),依赖于集合元素的正确排序的集合函数中的未定义行为。
在没有看到您的代码的情况下冒险猜测,我会说std::list
或std::vector
可能更适合您的应用程序。检查您将使用的操作和函数的复杂性,并确保所有这些操作和函数都存在于所选容器上。您可能需要依靠 std::algorithm
类来获得某些任务的帮助,但如果可能,请尝试坚持使用容器的内置功能。
我大体上同意 Yakk 和 Conduit 所说的话:认真地说,不要让你的比较器依赖于全局状态。 为了更权威地支持这一点,Herb Sutter和Andrei Alexandrescu的C++编码标准中的第87项说:
使谓词成为纯函数。
谓词纯度:谓词是返回是/否答案的函数对象,通常作为
bool
值。 如果一个函数的结果仅取决于其参数,那么它的结果在数学意义上是纯函数(请注意,这种"纯"的使用与纯虚函数无关)。不允许谓词保存或访问影响其
operator()
结果的状态,包括成员状态和全局状态。 更喜欢使operator()
成为谓词的const
成员函数。
我最想添加到讨论中的是一个简单的解决方案,以实现我认为应该首选的多个排序。
- 将所有元素(类型为
Foo
)存储在std::vector<Foo>
中,使用永不更改的任意顺序。 显而易见的选择当然是在它们发生时简单地push_back()
它们。 - 对于您希望使用的每个排序,定义一个纯排序函子
Compare
,该函子具有一个bool operator()(const Foo *, const Foo *) const;
和一个相应的std::set<Foo *, Compare>
(使用接受两个迭代器和一个比较器的构造函数),该是指向向量的指针的轻量级结构。 - 如果订单过时,只需处理相应的集合即可。
仅当您打算访问每个排序多次(按元素数量的顺序)次时,此方法才有价值。 如果您只想查找少量元素以进行各种排序,请考虑改用std::partial_sort
或std::partial_sort_copy
。 请考虑对复制成本低的指针进行排序std::vector<Foo *>
而不是原始Foo
对象。
现在对于一些代码:
#include <algorithm>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <set>
#include <string>
#include <utility>
#include <vector>
using Foo = std::pair<float, float>;
using FooIter = std::vector<Foo>::iterator;
std::ostream&
operator<<(std::ostream& os, const Foo& foo)
{
os << std::fixed << std::setprecision(4)
<< "["
<< std::setw(8) << foo.first
<< ", "
<< std::setw(8) << foo.second
<< "]";
return os;
}
// Ugly template, irrelevant for this discussin, don't use in production code.
template<typename ContainerT>
void
print_container(const ContainerT& container, const std::string& name)
{
std::cout << name << "nn";
for (const auto& it : container)
std::cout << *it << "n";
std::cout << std::endl;
}
int
main()
{
// Create a vector of 10 'Foo's.
std::vector<Foo> items {};
for (float x = 0.0f; x < 10.0f; x += 1.0f)
items.emplace_back(x, std::sin(x));
// For convenience, also create a vector of iterators into that vector.
std::vector<FooIter> item_iterators {};
for (auto it = items.begin(); it != items.end(); ++it)
item_iterators.push_back(it);
// Comparator based on the first element.
const auto cmp1 = [](const FooIter it1, const FooIter it2)->bool{
return it1->first < it2->first;
};
// Comparator based on the second element.
const auto cmp2 = [](const FooIter it1, const FooIter it2)->bool{
return it1->second < it2->second;
};
{
// Create a set ordered by the value of the first element.
std::set<FooIter, decltype(cmp1)> set1 {
item_iterators.begin(), item_iterators.end(), cmp1
};
print_container(set1, "set1");
}
{
// Create a set ordered by the value of the second element.
std::set<FooIter, decltype(cmp2)> set2 {
item_iterators.begin(), item_iterators.end(), cmp2
};
print_container(set2, "set2");
}
{
// Create a vector of the three smallest (by the first element) values.
std::vector<FooIter> vec1(3);
std::partial_sort_copy(item_iterators.begin(), item_iterators.end(),
vec1.begin(), vec1.end(), cmp1);
print_container(vec1, "vec1");
}
{
// Create a vector of the three smallest (by the second element) values.
std::vector<FooIter> vec2(3);
std::partial_sort_copy(item_iterators.begin(), item_iterators.end(),
vec2.begin(), vec2.end(), cmp2);
print_container(vec2, "vec2");
}
return 0;
}
输出为:
set1
[ 0.0000, 0.0000]
[ 1.0000, 0.8415]
[ 2.0000, 0.9093]
[ 3.0000, 0.1411]
[ 4.0000, -0.7568]
[ 5.0000, -0.9589]
[ 6.0000, -0.2794]
[ 7.0000, 0.6570]
[ 8.0000, 0.9894]
[ 9.0000, 0.4121]
set2
[ 5.0000, -0.9589]
[ 4.0000, -0.7568]
[ 6.0000, -0.2794]
[ 0.0000, 0.0000]
[ 3.0000, 0.1411]
[ 9.0000, 0.4121]
[ 7.0000, 0.6570]
[ 1.0000, 0.8415]
[ 2.0000, 0.9093]
[ 8.0000, 0.9894]
vec1
[ 0.0000, 0.0000]
[ 1.0000, 0.8415]
[ 2.0000, 0.9093]
vec2
[ 5.0000, -0.9589]
[ 4.0000, -0.7568]
[ 6.0000, -0.2794]
- std::设置自定义比较器
- C++中"std::sort"比较器的不同类型
- 将 std::set 与基于键的比较器一起使用
- 带自定义比较器的最小优先级队列
- 函数类作为比较器
- 优先级队列自定义比较器
- 什么是自定义比较器以及如何在 C++ 的排序函数中使用它?
- 没有默认构造函数作为模板参数的自定义比较器
- set_intersection使用自定义设置比较器
- 如何为集合 c++ 建立比较器
- C++复杂情况的比较器通过参数问题
- 对于BTreeMap和其他依赖于Ord的东西,是否有等效于C++比较器对象?
- 对没有比较器或λ函数的向量进行排序?
- "operator()"在重载运算符方法中是什么意思,在priority_queue(STL)中用作C++中的比较器?
- 用户定义的结构是否有默认C++比较器?
- C++设置了一个用于排序的比较器和另一个用于唯一性的比较器
- 使用迭代器的自定义比较器函数
- C++对λ比较器EXC_BAD_ACCESS进行排序
- 如果要求比较器是严格的总排序,而不仅仅是严格的弱排序,C++标准算法会更快吗?
- C++ std::set 使用基于时间的比较器