【问题标题】:Defining a map with custom comparator where the value datastructure also has custom comparator使用自定义比较器定义映射,其中值数据结构也具有自定义比较器
【发布时间】:2020-11-01 05:44:23
【问题描述】:

我想定义一个像这样的数据结构:

struct node {
    int x_coordinate;
    int y_coordinate;
    // some variables
};

map<node, priority_queue<node> > M;
// or lets say 
map<node, set<node> > M

我面临的问题是我不知道如何编写自定义比较器

您还认为是否可以根据与关键节点的距离对priority_queue 进行排序。例如,假设我有 x_coordinate=0 和 y_coordinate=0 的关键节点,我想插入 (8,6),(4,3),(15, 9),(0,1)。

所以 priority_queue 应该是 (0,1) (4,3) (8,6) (15,9)

附: : 与人讨论后,我使用了以下代码,但它仍然给出编译错误

struct Node {
    Node (int a, int b) {
        x = a;
        y = b;
    }
    int x, y;
};

struct cmp {
    Node node(0,0); // this node corresponds to the node that came from map key
    cmp(Node node) {
        this->node = node;
    }
    int getDistance (Node a, Node b) {
        return abs(a.x - b.x) + abs(a.y - b.y);
    }
    bool operator () (Node node1, Node node2) {
        return (getDistance(node, node1) < getDistance(node, node2));
    }

};

int main() {
    auto mapCmp = [&] (Node node1, Node node2){
        return node1.x < node2.x and (node1.x == node2.x and node1.y < node2.y);
    };
    map<Node, priority_queue<Node, vector<Node>, cmp(Node)>, decltype(mapCmp)> myMap(mapCmp);
    myMap[Node(0,0)].push(Node(2,4));
    myMap[Node(0,0)].push(Node(1,3));
    myMap[Node(0,1)].push(Node(2,4));
    myMap[Node(0,1)].push(Node(1,3));
    return 0;
}

错误快照:

【问题讨论】:

  • 类似这样的东西: bool cmp (node a, node b) { return a.x

标签: c++ c++11 data-structures stl


【解决方案1】:

有趣的问题。后一部分(基于映射源键值的优先级顺序)提出了真正的挑战。

映射自定义键比较

从键比较到映射的基本三种方法是:

  • 为映射键类型提供operator &lt; 成员覆盖,或者
  • 提供函子类型作为地图的比较器,或者
  • 提供免费功能。

其中最常见的是第一个,因为它最容易实现和可视化。映射的默认比较器是std::less&lt;K&gt;,其中K 是键类型。标准库std::less 默认尝试进行operator &lt; 比较,实际上它是这样做的:

bool isLess = (a < b)

其中ab 都是您的密钥类型。因此,一个简单的成员 const operator &lt; 重载将满足要求并为您提供所需的内容:

struct Node {
    Node(int a=0, int b=0)
        : x(a), y(b)
    {
    }

    int x, y;

    // called by default std::less
    bool operator <(const Node& rhs) const
    {
        return (x < rhs.x) || (!(rhs.x < x) && y < rhs.y);
    }
    
    // simple distance calculation between two points in 2D space.
    double distanceFrom(Node const& node) const
    {
        return std::sqrt(std::pow((x - node.x), 2.0) + std::pow((y - node.y), 2.0));
    }

    friend std::ostream& operator <<(std::ostream& os, Node const& node)
    {
        return os << '(' << node.x << ',' << node.y << ')';
    }
};

这支持严格的弱顺序并且就足够了。其他选项有点复杂,但不是很多。我不会在这里介绍它们,但是有很多关于 SO 的问题。

注意:我添加了distanceFromoperator &lt;&lt; 成员和朋友,以供以后在最终示例中使用;稍后您会看到它们。


优先队列实例比较覆盖

以前从未这样做过,但如果有更简单的方法,我当然愿意接受建议。为您的优先级队列使用模板类型的比较器覆盖的问题是您不能真正做到这一点。您希望根据到原点的距离对每个队列进行排序,其中原点是该队列的映射键。这意味着必须以某种方式为每个 comparison 对象提供映射键的来源,而您不能通过模板类型覆盖(即编译时的东西)来做到这一点。

然而,您可以做的是提供一个实例比较覆盖。 std::priority_queue 允许您提供自定义比较对象在哪里创建队列(在我们的例子中,当它作为映射目标插入到地图中时)。稍微按摩一下,我们想出了这个:

首先,一个不带参数或节点的优先级函子。

// instance-override type
struct NodePriority
{
    NodePriority() = default;

    NodePriority(Node node)
        : key(std::move(node))
    {
    }

    // compares distance to key of two nodes. We want these in
    // reverse order because smaller means closer means higher priority.
    bool operator()(const Node& lhs, const Node& rhs) const
    {
        return rhs.distanceFrom(key) < lhs.distanceFrom(key);
    }

private:
    Node key;
};

using NodeQueue = std::priority_queue<Node, std::deque<Node>, NodePriority>;

using NodeQueue 将为我们节省大量输入以下示例的时间。

示例

使用上述内容,我们现在可以构建地图和队列了。下面创建一个由十个节点组成的随机列表,每个节点的进位和 x,y 都在 1..9 的范围内。然后我们使用这些节点构建十个优先级队列,一个用于我们正在创建的每个映射条目。映射条目是对角线切片(即 (1,1)、(2,2)、(3,3) 等)。使用相同的十个随机元素,当我们报告最终结果时,我们应该会看到不同的优先级队列排序。

#include <iostream>
#include <vector>
#include <string>
#include <cstdlib>
#include <queue>
#include <map>
#include <random>

struct Node {
    Node(int a=0, int b=0)
        : x(a), y(b)
    {
    }

    int x, y;

    // called by default std::less
    bool operator <(const Node& rhs) const
    {
        return (x < rhs.x) || (!(rhs.x < x) && y < rhs.y);
    }

    // simple distance calculation between two points in 2D space.
    double distanceFrom(Node const& node) const
    {
        return std::sqrt(std::pow((x - node.x), 2.0) + std::pow((y - node.y), 2.0));
    }

    friend std::ostream& operator <<(std::ostream& os, Node const& node)
    {
        return os << '(' << node.x << ',' << node.y << ')';
    }
};

// instance-override type
struct NodePriority: public std::less<Node>
{
    NodePriority() = default;

    NodePriority(Node node)
        : key(std::move(node))
    {
    }

    // compares distance to key of two nodes. We want these in
    // reverse order because smaller means closer means higher priority.
    bool operator()(const Node& lhs, const Node& rhs) const
    {
        return rhs.distanceFrom(key) < lhs.distanceFrom(key);
    }

private:
    Node key;
};

using NodeQueue = std::priority_queue<Node, std::deque<Node>, NodePriority>;

int main()
{
    std::mt19937 rng{ 42 }; // replace with  { std::random_device{}() } for random sequencing;
    std::uniform_int_distribution<> dist(1, 9);

    std::map<Node, NodeQueue> myMap;

    // generate ten random points
    std::vector<Node> pts;
    for (int i = 0; i < 10; ++i)
        pts.emplace_back(Node(dist(rng), dist(rng)));

    for (int i = 0; i < 10; ++i)
    {
        Node node(i, i);
        myMap.insert(std::make_pair(node, NodeQueue(NodePriority(node))));
        for (auto const& pt : pts)
            myMap[node].emplace(pt);
    }

    // enumerate the map of nodes and their kids
    for (auto& pr : myMap)
    {
        std::cout << pr.first << " : {";
        if (!pr.second.empty())
        {
            std::cout << pr.second.top();
            pr.second.pop();
            while (!pr.second.empty())
            {
                std::cout << ',' << pr.second.top();
                pr.second.pop();
            }
        }
        std::cout << "}\n";
    }
}

注意:伪随机生成器始终以42 为种子,以具有可重复的序列。当您决定通过不可重复测试来放松这一点时,只需将该种子替换为声明旁边的注释中提供的种子即可。

输出(当然,您的输出会有所不同)。

(0,0) : {(3,1),(5,1),(3,5),(5,3),(5,4),(5,5),(1,9),(6,7),(5,8),(6,8)}
(1,1) : {(3,1),(5,1),(3,5),(5,3),(5,4),(5,5),(6,7),(1,9),(5,8),(6,8)}
(2,2) : {(3,1),(3,5),(5,1),(5,3),(5,4),(5,5),(6,7),(5,8),(1,9),(6,8)}
(3,3) : {(3,1),(3,5),(5,3),(5,4),(5,5),(5,1),(6,7),(5,8),(6,8),(1,9)}
(4,4) : {(5,4),(5,5),(3,5),(5,3),(5,1),(3,1),(6,7),(5,8),(6,8),(1,9)}
(5,5) : {(5,5),(5,4),(3,5),(5,3),(6,7),(5,8),(6,8),(5,1),(3,1),(1,9)}
(6,6) : {(6,7),(5,5),(6,8),(5,4),(5,8),(3,5),(5,3),(5,1),(1,9),(3,1)}
(7,7) : {(6,7),(6,8),(5,8),(5,5),(5,4),(3,5),(5,3),(1,9),(5,1),(3,1)}
(8,8) : {(6,8),(6,7),(5,8),(5,5),(5,4),(3,5),(5,3),(1,9),(5,1),(3,1)}
(9,9) : {(6,8),(6,7),(5,8),(5,5),(5,4),(3,5),(5,3),(1,9),(5,1),(3,1)}

我会让你验证 distanceFrom 计算的准确性,但希望你能明白。

【讨论】:

    【解决方案2】:
    struct cmp {
        cmp(Node node) { this->node = node; }
        bool operator () (const Node& node1, const Node& node2) {
            return (getDistance(node, node1) < getDistance(node, node2));
        }
    
        Node node; // this node corresponds to the node that came from map key
    };
    

    您可以将地图声明为

    std::map<Node, priority_queue<Node, vector<Node>, cmp(Node)>> myMap;
    

    下面的行使上面写的比较器与常规比较器不同,因为您想在比较器内部传递映射的键。

        cmp(Node node) { this->node = node; }
    

    我没有编译上面的。如果有任何错误应该使用微不足道的修复。我想我已经提供了足够的代码来解除对您的阻止。作为练习,尝试使用 lambda 而不是单独的比较器函数。

    【讨论】:

    • 我按照你说的写了,但它仍然给出了一些错误,你能帮我解决这个问题吗,我还添加了地图比较器,我认为它工作正常,但priority_queue的比较器给我带来了麻烦
    • 这不会编译,因为cmp(Node)myMap 的第三个模板参数不是 type。这可以在构造插入到映射中的优先级队列时使用实例覆盖来纠正。多做一些工作,但我认为可行。
    • 是的,我只是想给出如何去做。提供完整的可编译的东西对学习没有多大帮助。所以我说如果有任何错误,它应该可以通过简单的修复来解决。在修复编译错误的过程中,学到了很多东西。
    猜你喜欢
    • 1970-01-01
    • 2017-11-02
    • 2018-10-31
    • 2016-10-20
    • 2018-12-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多