STL排序集,其中的排序条件可能会发生变化

STL sorted set where the conditions of order may change

本文关键字:排序 变化 STL 条件      更新时间:2023-10-16

我有一个定义了自定义顺序的C++STL集。

当时的想法是,当项目被添加到集合中时,它们会自然地按照我的意愿进行订购。

然而,我刚刚意识到,排序谓词可以随着时间的推移而改变

据推测,集合中的项目将不再按顺序排列。

所以有两个问题:

  1. 这些物品会出现故障,这有害吗?我说得对吗?最糟糕的情况是,新的参赛作品可能会被放错地方(实际上我可以接受)。或者,这会导致崩溃、条目丢失等?

  2. 有没有办法"刷新"套装的顺序?您似乎不能在集合上使用std::sort()。我能想到的最好的办法就是把里面的东西倒到一个临时容器里,然后重新添加。

有什么想法吗?

谢谢,

John

set使用排序来查找项目。如果您要根据ordering1插入N个项目,并根据ordering2插入一个项目,则集合无法确定该项目是否已在。

它将违反类不变量,即每个项只在其中一次。

所以它危害。

使用STL执行此操作的唯一安全方法是使用更改的谓词创建一个新集合。例如,当你需要用一个新的谓词对集合进行排序时,你可以这样做:

std::set<int> newset( oldset.begin(), oldset.end(), NewPred() );

这实际上取决于实现
STL实现可以而且通常会假设用于排序的谓词是稳定的(否则,将不会定义"已排序")。当您更改谓词实例的行为时,至少可以构造一个有效的STL实现来格式化您的硬盘驱动器。

因此,是的,您需要将这些项目重新插入到一个新的集合中。

或者,您可以构建自己的容器,例如用于二进制搜索的向量+排序+下限。然后,您可以在谓词行为发生变化时重新排序。

我同意其他答案,即这将以一些奇怪且难以调试的方式中断。如果采用刷新路线,则只需要执行一次复制。使用新的排序策略创建一个tmp集合,将原始集合中的每个元素添加到tmp集合中,然后进行

 orig.swap(tmp);

这将交换集合的内部。

如果是我,我会把它封装在一个处理所有细节的新类中,这样您就可以根据需要更改实现。根据您的访问模式和排序顺序更改的次数,前面提到的向量、排序、低位解决方案可能更可取。

如果您可以使用无序集,那么为什么要首先将它们添加到集中?

我唯一能想到的情况是,当你添加列表时,你只想确保列表是唯一的。如果是这样的话,那么你可以使用一个临时设置来保护添加:

if (ts.insert (value).second) {
    // insertion took place
    realContainer.push_back (value);
}

另一种选择是,根据您修改集合中条目的频率,您可能可以测试条目是否在不同的位置(通过使用集合比较功能)以及位置将移动到哪里,然后删除旧条目并重新添加新条目。

正如其他人所指出的那样——让集合无序真的很难闻——我还猜测,根据标准,它可能会导致未定义的行为

虽然这并不能完全满足您的需求,但boost::multi_index为您提供了类似的功能。由于模板的工作方式,您永远无法"更改"容器的排序谓词,它在编译时是固定不变的,除非您使用排序向量或类似的东西,否则是保持不变的,您可以在任何给定时间按需排序。

但是,Multi_index为您提供了一种同时基于多个排序谓词对一组元素进行排序的方法。然后,您可以选择容器的视图,这些视图的行为类似于您当时关心的谓词排序的std::set。

这可能会导致条目丢失,当在set中搜索元素时,会使用排序运算符,这意味着如果一个元素被放置在根的左边,而现在排序运算符说它在右边,则该元素将不再被找到。

这里有一个简单的测试:

struct comparer : public std::binary_function<int, int, bool>
{
  static enum CompareType {CT_LESS, CT_GREATER} CompareMode;
  bool operator()(int lhs, int rhs) const
  {
    if(CompareMode == CT_LESS)
    {
      return lhs < rhs;
    }
    else
    {
      return lhs > rhs;
    }
  }
};
comparer::CompareType comparer::CompareMode = comparer::CT_LESS;
typedef std::set<int, comparer> is_compare_t;
void check(const is_compare_t &is, int v)
{
  is_compare_t::const_iterator it = is.find(v);
  if(it != is.end())
  {
    std::cout << "HAS " << v << std::endl;
  }
  else
  {
    std::cout << "ERROR NO " << v << std::endl;
  }
}
int main()
{
  is_compare_t is;
  is.insert(20);
  is.insert(5);
  check(is, 5);
  comparer::CompareMode = comparer::CT_GREATER;
  check(is, 5);
  is.insert(27);
  check(is, 27);
  comparer::CompareMode = comparer::CT_LESS;
  check(is, 5);
  check(is, 27);
  return 0;
}

因此,基本上,如果您打算查找您曾经插入的元素,则不应更改用于插入和查找的谓词。

只是一个后续:

在运行此代码时,Visual Studio C调试库开始抛出异常,抱怨"<"运算符无效。

因此,改变排序似乎是一件坏事。谢谢大家!

1)有害-否。导致崩溃-否。最糟糕的情况确实是未排序的集合。

2) "刷新"和重新添加是一样的!