std :: set用户定义的类型,如何确保没有重复

时间:2009-07-11 22:50:08

标签: c++ set

所以我有一个std :: set,它需要保持特定的排序以及不允许用户定义(由我)类型的重复。现在我可以通过重载'<'来使订单正常工作我的类型的运算符。但是,该集合没有适当地检测重复,并且说实话,我不完全确定它是如何在内部执行的。我已经重载'=='运算符,但不知何故我不确定这是该集实际使用的是什么?所以问题是当你添加值时,集合如何确定重复?以下是相关代码:

用户定义的类型:

//! An element used in the route calculation.
struct RouteElem {
    int shortestToHere; // Shortest distance from the start.
    int heuristic;      // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
        return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
        return (position.x == other.position.x && position.y == other.position.y);
    }
};

因此,当元素的位置相等时,元素是等价的,如果元素的组合函数小于另一元素,则元素小于另一元素。排序工作,但该集合将接受相同位置的两个元素。

6 个答案:

答案 0 :(得分:101)

operator==未使用

std::set。如果a

,则b!(a < b) && !(b < a)元素被视为相等

答案 1 :(得分:31)

std::set支持指定比较函数。默认值为less,它将使用operator <来检查相等性。您可以定义一个自定义函数来检查相等性,然后使用该函数:

std::set<RouteElem, mycomparefunction> myset; 

请注意,无法将比较功能与排序功能分开。 std::set是二叉树,如果二叉树中的元素不大于特定元素,则它应该在同一个位置。它在地方寻找算法中做了类似的事情:

if (a < b) {
    // check the left subtree
} else if (b < a) {
    // check the right subtree
} else {
    // the element should be placed here.
}

答案 2 :(得分:7)

rlbond的比较器不会阻止插入比较相等的元素。显然,鉴于字符限制,很难在评论中证明这一点,因为rlbond似乎认为std :: set保证它永远不会包含两个带有!compare(a,b) && !compare(b,a)元素的比较器。但是,rlbond的比较器没有定义严格的顺序,因此不是std :: set的有效参数。

#include <set>
#include <iostream>
#include <iterator>
#include <algorithm>

struct BrokenOrder {
    int order;
    int equality;

    public:
    BrokenOrder(int o, int e) : order(o), equality(e) {}

    bool operator<(const BrokenOrder &rhs) const {
        return order < rhs.order;
    }
    bool operator==(const BrokenOrder &rhs) const {
        return equality == rhs.equality;
    }
};

std::ostream &operator<<(std::ostream &stream, const BrokenOrder &b) {
    return stream << b.equality;
}

// rlbond's magic comparator
struct LessThan : public std::binary_function<BrokenOrder, BrokenOrder, bool> {
    bool operator()(const BrokenOrder& lhs, const BrokenOrder& rhs) const
    {
        return !(lhs == rhs) && (lhs < rhs);
    }
};

int main() {
    std::set<BrokenOrder,LessThan> s;
    for (int i = 0; i < 5; ++i) {
        s.insert(BrokenOrder(i,i));
    }
    for (int i = 0; i < 5; ++i) {
        s.insert(BrokenOrder(10-i,i));
    }
    std::copy(s.begin(), s.end(), 
        std::ostream_iterator<BrokenOrder>(std::cout, "\n"));
}

输出:

0
1
2
3
4
3
2
1
0

重复。神奇的比较器失败了。集合中的不同元素具有相同的equality值,因此将其与operator==进行比较,因为在插入期间,集合从未将新元素与其副本进行比较。排除的唯一重复是4,因为两个4的排序顺序为4和6.这使得它们在集合中足够接近,以便相互比较。

从C ++标准:25.3:3“为了使算法正常工作, comp 必须对值进行严格的弱排序”。

25.3:4“......要求是comp和equiv都是传递关系:

comp(a,b) && comp(b,c) implies comp(a,c)"

现在,考虑元素a = BrokenOrder(1,1)b = BrokenOrder(2,2)c = BrokenOrder(9,1),以及comp当然等于魔术比较器。然后:

  • comp(a,b)是真的,因为1!= 2(相等)和1 <1 2(订单)
  • comp(b,c)是真的,因为2!= 1(相等)和2&lt; 9(订单)
  • comp(a,c)因为1 == 1(相等)
  • 而为假

答案 3 :(得分:4)

STL集实现在概念上做了类似的事情来检测相等性:

bool equal = !(a < b) && !(b < a);

也就是说,如果两个元素都不小于另一个元素,那么它们必须相等。您可以通过在operator==()方法上设置断点并检查它是否被调用来检查这一点。

我通常会怀疑检查完全不同的事情的比较运算符。您的<运算符是根据与==运算符的定义方式不同的两个方面定义的。通常,您需要进行此类比较以使用一致的信息。

答案 4 :(得分:2)

您可以尝试以下内容:

//! An element used in the route calculation.
struct RouteElem {
    int shortestToHere; // Shortest distance from the start.
    int heuristic;              // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
      return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
      return (position.x == other.position.x && position.y == other.position.y);
    }
};

struct CompareByPosition {
    bool operator()(const RouteElem &lhs, const RouteElem &rhs) {
        if (lhs.position.x != rhs.position.x) 
            return lhs.position.x < rhs.position.x;
        return lhs.position.y < rhs.position.y;
    }
};

// first, use std::set to remove duplicates
std::set<RouteElem,CompareByPosition> routeset;
// ... add each RouteElem to the set ...

// now copy the RouteElems into a vector
std::vector<RouteElem> routevec(routeset.begin(), routeset.end());

// now sort via operator<
std::sort(routevec.begin(), routevec.end());

显然中间有副本,看起来很慢。但是,根据两个不同标准对项目进行索引的任何结构因此与一组相比每个项目将具有某种额外开销。上面的整个代码是O(n log n),假设你的std :: sort实现使用了introsort。

如果您拥有它,在此方案下,您可以使用unordered_set代替set来进行初始唯一化。由于哈希只需依赖于x和y,因此它应该比插入集合所需的O(log N)比较更快。

编辑:只是注意到你说你想“保持”排序顺序,而不是你想要批量处理所有内容。对于那个很抱歉。如果你想在添加元素的同时有效地维护订单并排除重复,那么我建议使用我在上面定义的基于位置的集合或无序集合,以及std::multiset<RouteElem>,它将维护operator<订购。对于每个新元素,请执行:

if (routeset.insert(elem).second) {
    routemultiset.insert(elem);
}

虽然请注意,这不提供任何例外保证。如果第二个插入抛出,则路径集已被修改,因此状态不再一致。所以我想你真的需要:

if (routeset.insert(elem).second) {
    try {
        routemultiset.insert(elem); // I assume strong exception guarantee
    } catch(...) {
        routeset.erase(elem); // I assume nothrow. Maybe should check those.
        throw;
    }
}

或者是RAII的等价物,如果你的代码中只有一个地方你曾经使用过RAII类,那么它将会更加冗长,但如果重复得多则会更好。

答案 5 :(得分:0)

要注意这种情况的后果。看起来你正在尝试做类似A *的事情,如果你试图插入一个“重复”,它将被忽略,即使有一条“更好”的路线。

注意:此解决方案不起作用,请参阅下面的onebyone说明

struct RouteElem 
{
    int shortestToHere; // Shortest distance from the start.
    int heuristic;              // The heuristic estimate to the goal.
    Coordinate position;
    bool operator<( const RouteElem& other ) const
    {
        return (heuristic+shortestToHere) < (other.heuristic+other.shortestToHere);
    }
    bool operator==( const RouteElem& other ) const
    {
        return (position.x == other.position.x && position.y == other.position.y);
    }
};

struct RouteElemLessThan : public std::binary_function<RouteElem, RouteElem, bool>
{
    bool operator()(const RouteElem& lhs, const RouteElem& rhs) const
    {
        return !(lhs == rhs) && (lhs < rhs);
    }
};

std::set<RouteElem, RouteElemLessThan> my_set;