【问题标题】:std::set has duplicate entrystd::set 有重复条目
【发布时间】:2015-05-21 02:12:58
【问题描述】:

我有一组 3 个整数的元组,我不想要任何重复项。也就是说,我不希望 2 个条目具有相同的 3 个值。

这是我的代码。

struct Key{
    unsigned a;
    unsigned b;
    unsigned c;
  public:
    Key(unsigned _a, unsigned _b, unsigned _c) :
        a(_a),
        b(_b),
        c(_c) {}
    bool operator<(const Key& rhs) const
    {
        if (a < rhs.a) {
            return true;
        }
        if (b < rhs.b) {
            return true;
        }
        if (c < rhs.c) {
            return true;
        }
        return false;
    };
};
std::set<Key> myset;

但我有时会在myset 中看到重复项。我无法准确掌握导致添加重复条目的顺序。它并不总是发生。 我的问题是,我的 operator&lt; 函数是否存在本质问题?

【问题讨论】:

  • 你能发布一些重复的值吗?
  • 很好地为这个问题提出了正确的初始问题——通常是一个损坏的严格弱排序确实会导致这种情况。如果您以奇怪的方式使用该套装,您可能在其他地方也有 UB。
  • 我试图简化我的问题。我测试的集合实际上具有元组的第一个元素,类型为“boost::asio::ip::address_v4”。鉴于我在集合中看到两次 (172.20.20.10, 4077, 17) 条目。

标签: c++ stl


【解决方案1】:

几乎是对的!但是你级联的太快了。

bool operator<(const Key& rhs) const
{
    if (a < rhs.a)
        return true;
    if (a > rhs.a)
        return false;

    if (b < rhs.b)
        return true;
    if (b > rhs.b)
        return false;

    return (c < rhs.c);
};

否则,例如,以下会给出错误的结果:

Key lhs{3,1,0};
Key rhs{2,2,0};

assert(lhs < rhs); // passes, wrongly, because !(3 < 2) but then (1 < 2).
                   // you need an immediate `return false` when !(3 < 2)

这样做更安全:

bool operator<(const Key& rhs) const
{
    return std::tie(a, b, c) < std::tie(rhs.a, rhs.b, rhs.c);
}

C++ 的标准库已经知道如何处理它,所以您不必这样做。


现在,您的错误如何导致 set 中的键重复?

Set 的内部算法依赖于严格的弱排序——当你打破这个前提条件时,你就打破了管理内部树的算法,内部树是使用这个排序作为它的圣经来构建和排列的。

基本上,所有的地狱都崩溃了。您可能会因此而崩溃。在您的情况下,症状稍微温和一些(至少现在是这样),数据树变形/损坏导致重复数据的外观

如果你一开始就打破了先决条件并导致了 UB,那么试图推理导致特定结果的特定事件链是愚蠢的。


【讨论】:

  • 谢谢。我发现排序是个问题,但我还不明白是否存在重复。
  • @SindhuraBandi:我想你已经明白了,因为你已经做出了逻辑推论来检查比较器。我已经在答案中添加了原因。
  • 是的,我只是想形成一个示例序列。我现在知道了,非常感谢您的快速回复。
  • @SindhuraBandi:是的,很遗憾我无法复制它。有可能推导出来,但需要付出很大的努力。 :)
  • @LightnessRacesinOrbit 关键是标准通常只依赖&lt; 对元素进行字典比较,而不是其他比较运算符(例如,operator&lt; 用于元组,std::lexicographical_compare 等。 )。当然,由于这些都是整数,所以这里没有区别。
【解决方案2】:

您的operator&lt;() 不一致,因为key1&lt;key2key2&lt;key1 可能都为true(例如:key1={1,3,0}key2={3,1,0})。你应该给成员变量一个比较的优先级:

    if (a < rhs.a) {
        return true;
    } else if (a == rhs.a) {
        if (b < rhs.b) {
            return true;
        } else if (b == rhs.b) {
            if (c < rhs.c) {
                return true;
            }
        }
    }
    return false;

【讨论】:

  • 感谢您的澄清。我看到我的比较函数有一些排序问题。但是它可以接受重复吗?
  • cpp reference 声明相等性由!comp(a,b) &amp;&amp; !comp(b,a) 检查,这也应避免与您的operator&lt;() 重复。不知道为什么你会看到重复。
  • @jhnnslschnr:除非订购满足前提条件,否则它是UB。您关于!comp(a,b) &amp;&amp; !comp(b,a) 的断言是false,因为comp(a,b) == comp(b,a) 的顺序不正确,这显然是荒谬的。看我的回答。
  • @LightnessRacesinOrbit 好的,UB 是一个公平的观点,任何事情都有可能发生。
  • 对于不相等的ab,我的意思是:D
【解决方案3】:

您确实可以使用标准类std::tuple 作为键。

尽管如此,操作符可以通过以下方式定义

bool operator <( const Key &rhs ) const
{
    return
    ( a < rhs.a ) ||
    ( !( rhs.a < a ) && ( b < rhs.b ) ) ||
    ( !( rhs.a < a ) && !( rhs.b < b ) && ( c < rhs.c ) );
};

这个操作符可以工作,你只需要为对象 a、b 和 c 的类型定义operator &lt; 当然对于算术类型它已经定义了。

其实是一样的

#include <tuple>

//...

bool operator <( const Key &rhs ) const
{
    return std::tie( a, b, c ) < std::tie( rhs.a, rhs.b, rhs.c );
}

【讨论】:

    猜你喜欢
    • 2020-09-06
    • 1970-01-01
    • 2014-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-03
    相关资源
    最近更新 更多