【问题标题】:Using comparator to sort set of unique pairs with different criterias for uniqueness and less than使用比较器对具有不同唯一性和小于标准的唯一对进行排序
【发布时间】:2022-01-09 07:27:54
【问题描述】:

首先,我尝试搜索类似的问题,但没有找到任何解释我的问题的答案。

问题如下:给定一组坐标为 (x,y,z) 的 N 个节点,使用第 4 个值 F 尽可能快地对它们进行排序。

我想为此使用带有自定义比较器的std::set,因为它具有 O(log(N)) 复杂度。我知道我也可以尝试std::vector 并在std::vector 上调用std::sort,但理论上操作速度较慢。

为什么会这样?因为我不断地在集合中插入元素,更改 F 值(这意味着我更改了值并重新排序容器中的元素,我删除并重新插入它)并且我想采取F值较小的元素(即容器最前面的元素)。

但是让我们来解决std::set 的问题。

坐标定义了唯一性属性,遵循严格的弱排序规则,这意味着ab如果被认为是同一个对象

!comp(a,b) && !comp(b,a)

问题与定义基于坐标的唯一性标准和基于 F 值的排序标准有关。我不希望集合存储具有相同坐标的两个元素,但我希望它允许存储具有不同坐标但 F 值相同的两个元素

比较器还应满足以下三个属性:

  1. 非自反性 x < x false
  2. 不对称x < y true 暗示y < x false
  3. 传递性x < y && y < z 暗示x < z true

所以知道了所有这些属性,我一直在使用以下示例实现:

一些定义

class Node;
struct NodeComparator;
using NodePair = std::pair<Node *, int>;
using NodeSet  = std::set<NodePair, NodeComparator>;

为了方便,这里我使用指针

类节点

class Node
{

public:
    Node()
    {
    }
    Node(int _x, int _y, int _z, int _val) : x(_x), y(_y), z(_z), value(_val)
    {
    }

    int x, y, z;
    int value;

    friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
    {
        os << "[" << dt.x << ", " << dt.y << ", " << dt.z << "], [" << dt.value << "]";
        return os;
    }
    friend bool operator==(const Node &_lhs, const Node &_rhs){
        if( _lhs.x == _rhs.x &&
            _lhs.y == _rhs.y &&
            _lhs.z == _rhs.z ){
                return true;
            }
        return false;
    }
};

这里操作符&lt;&lt;被重载仅用于调试目的

比较器


struct NodeComparator
{
    bool operator()(const NodePair &_lhs, const NodePair &_rhs) const
    {
        if( _lhs.first == nullptr || _rhs.first == nullptr )
            return false;
        /*
        This first check implements uniqueness. 
        If _lhs == _rhs --> comp(_lhs,_rhs) == false && comp(_rhs, _lhs) == false
        So ( !comp(l,r) && !comp(r,l) ) == true
        */
        if( *_lhs.first == *_rhs.first) 
            return false;
        
        int ret = _lhs.second - _rhs.second;
        return ret < 0;
    }
};

我猜一个问题可能是两个节点坐标不同但 F 值相同的情况

具体案例的完整示例

Ì在这个例子中,我使用上面的类来插入/查找/删除一些元素,但是它显示在输出中,它的行为不像预期的那样:

#include <iostream>
#include <set>
#include <vector>
#include <algorithm>
#include <tuple>

class Node;
struct NodeComparator;
using NodePair = std::pair<Node *, int>;
using NodeSet  = std::set<NodePair, NodeComparator>;
class Node
{

public:
    Node()
    {
    }
    Node(int _x, int _y, int _z, int _val) : x(_x), y(_y), z(_z), value(_val)
    {
    }

    int x, y, z;
    int value;

    friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
    {
        os << "[" << dt.x << ", " << dt.y << ", " << dt.z << "], [" << dt.value << "]";
        return os;
    }
};

struct NodeComparator
{
    bool operator()(const NodePair &_lhs, const NodePair &_rhs) const
    {
        /*
        This first check implements uniqueness. 
        If _lhs == _rhs --> comp(_lhs,_rhs) == false && comp(_rhs, _lhs) == false
        So ( !comp(l,r) && !comp(r,l) ) == true
        */
        if(_lhs == _rhs) 
            return false;
        
        int ret = _lhs.second - _rhs.second;
        return ret < 0;
    }
};
int main(int argc, char **argv)
{
    Node n1(0, 2, 4, 12), 
         n2(2, 4, 5, 25), 
         n3(0, 1, 4, 34), 
         n4(0, 1, 4, 20), 
         n5(0, 1, 5, 20),
         n6(0, 2, 4, 112);

    NodeSet set;

    set.insert({&n1, n1.value});
    set.insert({&n2, n2.value});
    set.insert({&n3, n3.value});
    set.insert({&n4, n4.value}); //Should not be inserted because it already exists n3 with same coords
    set.insert({&n5, n5.value});

    //Try to insert multiple times a previously inserted node (n1 coords is == n6 coords)
    //It should not be inserted because it already exists one node with the same coords (n1)
    set.insert({&n6, n6.value});
    set.insert({&n6, n6.value});
    set.insert({&n6, n6.value});
    set.insert({&n6, n6.value});
    set.insert({&n6, 0});
    set.insert({&n6, 1});

    if (set.find({&n4, n4.value}) != set.end())
        std::cout << "Found n4" << std::endl;
    
    auto it = set.erase({&n4, 20});
    std::cout << "It value (elements erased): " << it << std::endl;

    if (set.find({&n4, n4.value}) != set.end())
        std::cout << "Found n4 after removal" << std::endl;
    
    std::cout << "Final Set content: " << std::endl;
    for (auto &it : set)
        std::cout << *it.first << std::endl;


    return 0;
}

用C++11或以上版本编译:g++ -o main main.cpp

输出:

Found n4
It value (elements erased): 1
Final Set content: 
[0, 2, 4], [12]
[2, 4, 5], [25]
[0, 1, 4], [34]
[0, 2, 4], [112]

**预期输出:**对应于元素 n1、n5、n2、n3,从 F (n1) 较小的元素到 F (n3) 较大的元素排序。

Final Set content: 
[0, 2, 4], [12]
[0, 1, 5], [20]
[2, 4, 5], [25]
[0, 1, 4], [34]

我将不胜感激任何帮助或想法和实施替代方案。谢谢

【问题讨论】:

  • Node 已经包含您要与比较器一起使用的值时,您不需要存储NodePair
  • 当你关心运行时,你也应该打开编译器优化。
  • 请在问题中包含预期的输出
  • operator&lt;(const Node&amp;,const Node&amp;) 不会在您的代码中的任何地方使用。如果您告诉集合使用NodeComparator 作为比较器,那么这就是集合用来确定两个元素是否等效
  • 如果您想说“如果之前插入了具有相同{x, y, z} 的另一个节点,则不要插入节点”,那么我建议第二个std::set,由{x, y, z} 排序.尝试在第二组中插入一个节点,并且仅当成功(意味着没有具有相同坐标的先前节点)插入到由F 排序的第一组中。或者查看Boost multi-index 之类的内容,了解可能同时具有多个迭代顺序的集合。

标签: c++ sorting stl strict-weak-ordering


【解决方案1】:

很遗憾,仅凭一个std::set 无法满足您的要求。 std::set 使用相同的比较器进行排序和唯一性。比较器没有状态,这意味着您不能将一次与第一个条件进行比较,而将下一次与第二个条件进行比较。那是行不通的。

因此,您需要使用 2 个容器,例如第一个 std::unordered_set 使用比较器进行相等坐标,第二个容器用于排序,例如 std::multiset..

您还可以将std::unordered_mapstd::multiset 结合使用。

或者您将自己的容器创建为一个类并尝试优化性能。

让我向您展示一个使用std::unordered_setstd::multiset 组合的示例。它会很快,因为std::unordered_set 使用哈希。

#include <iostream>
#include <unordered_set>
#include <set>
#include <array>
#include <vector>

using Coordinate = std::array<int, 3>;

struct Node {
    Coordinate coordinate{};
    int value{};
    bool operator == (const Node& other) const { return coordinate == other.coordinate; }
    friend std::ostream& operator << (std::ostream& os, const Node& n) {
        return os << "[" << n.coordinate[0] << ", " << n.coordinate[1] << ", " << n.coordinate[2] << "], [" << n.value << "]"; }
};
struct CompareOnSecond { bool operator ()(const Node& n1, const Node& n2)const { return n1.value < n2.value; } };
struct Hash {size_t operator()(const Node& n) const {return n.coordinate[0] ^ n.coordinate[1] ^ n.coordinate[2];} };

using UniqueNodes = std::unordered_set<Node, Hash>;
using Sorter = std::multiset<Node, CompareOnSecond>;

int main() {
    // a vector with some test nodes
    std::vector<Node> testNodes{
    { {{0, 2, 4}}, 12 },
    { {{2, 4, 5}}, 25 },
    { {{0, 1, 4}}, 34 },
    { {{0, 1, 4}}, 20 },
    { {{0, 1, 5}}, 20 },
    { {{0, 2, 4}}, 112 } };

    // Here we will store the unique nodes
    UniqueNodes uniqueNodes{};
    for (const Node& n : testNodes) uniqueNodes.insert(n);

    // And now, do the sorting
    Sorter sortedNodes(uniqueNodes.begin(), uniqueNodes.end());

    // Some test functions
    std::cout << "\nSorted unique nodes:\n";
    for (const Node& n : sortedNodes) std::cout << n << '\n';

    // find a node
    if (sortedNodes.find({ {{0, 1, 4}}, 20 }) != sortedNodes.end())
        std::cout << "\nFound n4\n";

    // Erase a node
    auto it = sortedNodes.erase({ {{0, 1, 4}}, 20 });
    std::cout << "It value (elements erased): " << it << '\n';

    // Was it really erased?
    if (sortedNodes.find({ {{0, 1, 4}}, 20 }) != sortedNodes.end())
        std::cout << "\nFound n4 after removal\n";

    // Show final result
    std::cout << "\nFinal Set content:\n";
    for (const Node& n : sortedNodes) std::cout << n << '\n';
}

【讨论】:

  • 首先,感谢您的精心回复。您确认我的实施无法满足我的要求,这对我很有帮助。您使用两个容器的答案正是解决问题的方法。但是,我尝试了它,并考虑到我应该将它集成到更大的代码库中,我最终按照@Igor Tandetnik 的建议使用 Boost::multi_index 容器实现了一个解决方案,并且效果很好。我会接受你的回答,但我也会使用 boost multi index 发布我的解决方案来帮助其他人
【解决方案2】:

最后,感谢用户的建议和 cmets,我使用 Boost multi index with 2 index 实现了一个解决方案。一个散列唯一索引和一个有序非唯一索引。尽管如此,我还是将上面的答案标记为已接受,因为这是最标准的解决方案。

#include <iostream>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/identity.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/key_extractors.hpp>

using namespace ::boost;
using namespace ::boost::multi_index;

struct IndexByCost {};
struct IndexByWorldPosition {};

class Node
{

public:
    Node(int _val, int _i) : value(_val), index(_i) {}

    int value; //NON UNIQUE
    unsigned int index; //UNIQUE

    friend inline std::ostream &operator<<(std::ostream &os, const Node &dt)
    {
        os << dt.index << ": [" << dt.value << "]";
        return os;
    }

};

using MagicalMultiSet = boost::multi_index_container<
  Node*, // the data type stored
  boost::multi_index::indexed_by< // list of indexes
    boost::multi_index::hashed_unique<  //hashed index wo
      boost::multi_index::tag<IndexByWorldPosition>, // give that index a name
      boost::multi_index::member<Node, unsigned int, &Node::index> // what will be the index's key
    >,
    boost::multi_index::ordered_non_unique<  //ordered index over 'i1'
      boost::multi_index::tag<IndexByCost>, // give that index a name
      boost::multi_index::member<Node, int, &Node::value> // what will be the index's key
    >
  >
>;

int main(int argc, char const *argv[])
{
    
    MagicalMultiSet container;

    Node n1{24, 1};
    Node n2{12, 2};
    Node n3{214,3};
    Node n4{224,4};
    Node n5{221,5};
    Node n6{221,6};

    auto & indexByCost          = container.get<IndexByCost>();
    auto & indexByWorldPosition = container.get<IndexByWorldPosition>();

    indexByCost.insert(&n1);
    indexByCost.insert(&n2);
    indexByCost.insert(&n3);
    indexByCost.insert(&n4);
    indexByCost.insert(&n5);

    for(auto &it: indexByCost)
        std::cout << *it << std::endl;
    
    auto it = indexByCost.begin();
    std::cout << "Best Node " << **it << std::endl;

    indexByCost.erase(indexByCost.begin());

    it = indexByCost.begin();
    std::cout << "Best Node After erasing the first one: " << **it << std::endl;

    std::cout << "What if we modify the value of the nodes?" << std::endl;
    n2.value = 1;
    std::cout << "Container view from index by world position" << std::endl;
    for(auto &it: indexByWorldPosition)
        std::cout << *it << std::endl;
    
    auto found = indexByWorldPosition.find(2);
    if(found != indexByWorldPosition.end() )
        std::cout << "Okey found n2 by index" << std::endl;

    found = indexByWorldPosition.find(1);
    if(found != indexByWorldPosition.end() )
        std::cout << "Okey found n1 by index" << std::endl;
    
    std::cout << "Imagine we update the n1 cost" << std::endl;
    n1.value = 10000;
    indexByWorldPosition.erase(found);
    indexByWorldPosition.insert(&n1);

    std::cout << "Container view from index by cost " << std::endl;

    for(auto &it: indexByCost)
        std::cout << *it << std::endl;
    
    return 0;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多