使用自定义比较器定义映射,其中值数据结构也具有自定义比较器
Defining a map with custom comparator where the value datastructure also has custom comparator
我想定义一个数据结构:
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)
P.S。 : 我在与人讨论后使用了以下代码,但它仍然给出编译错误
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;
}
错误快照:
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 而不是单独的比较器函数。
有趣的问题。后一部分(基于 mapped-from 键值的优先顺序)提出了真正的挑战。
映射自定义键比较
从键比较到映射的三种基本方法是:
- 为映射键类型提供
operator <
成员覆盖,或者
- 提供一个仿函数类型作为映射的比较器,或者
- 提供一个free-function.
其中最常见的是第一个,因为它最容易实现和可视化。映射的默认比较器是 std::less<K>
,其中 K
是键类型。标准库 std::less
默认尝试进行 operator <
比较,实际上它是这样做的:
bool isLess = (a < b)
其中 a
和 b
都是您的密钥类型。因此,一个简单的 member const operator <
重载就可以满足要求并得到你想要的:
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 的问题。
注意:我添加了 distanceFrom
和 operator <<
成员和朋友以供以后在最终示例中使用;稍后你会看到它们。
优先级队列实例比较覆盖
以前从未这样做过,但如果有更简单的方法,我当然愿意接受建议。为您的优先级队列使用 template-type 比较器覆盖的问题是您不能真正做到这一点。您希望每个队列根据到原点的距离进行排序,其中原点是该队列的映射键。这意味着每个 comparison 对象必须以某种方式提供地图键的原点,而你不能用 template-type 覆盖来做到这一点(即 compile-time 东西).
但是,您可以 做的是提供一个实例 比较覆盖。 std::priority_queue
允许您提供自定义比较对象 创建队列的地方 (在我们的例子中,当它作为 mapped-to 目标插入地图时)。稍微按摩一下,我们想出了这个:
首先,一个不带参数或节点的优先仿函数。
// 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
将为我们节省大量输入示例的时间。
示例
使用上面的内容,我们现在可以构建我们的地图和队列了。下面创建一个包含 10 个节点的随机列表,每个节点都携带 1..9 范围内的 x,y。然后我们使用这些节点构建十个优先级队列,一个对应于我们正在创建的每个映射条目。地图条目是对角线切片(即(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
播种以具有可重复的序列。当您决定通过 non-repeatable 测试来解决这个问题时,只需将该种子替换为声明旁边注释中提供的种子即可。
输出(当然,你的会有所不同)。
(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 计算的准确性,但希望你明白了。
我想定义一个数据结构:
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)
P.S。 : 我在与人讨论后使用了以下代码,但它仍然给出编译错误
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;
}
错误快照:
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 而不是单独的比较器函数。
有趣的问题。后一部分(基于 mapped-from 键值的优先顺序)提出了真正的挑战。
映射自定义键比较
从键比较到映射的三种基本方法是:
- 为映射键类型提供
operator <
成员覆盖,或者 - 提供一个仿函数类型作为映射的比较器,或者
- 提供一个free-function.
其中最常见的是第一个,因为它最容易实现和可视化。映射的默认比较器是 std::less<K>
,其中 K
是键类型。标准库 std::less
默认尝试进行 operator <
比较,实际上它是这样做的:
bool isLess = (a < b)
其中 a
和 b
都是您的密钥类型。因此,一个简单的 member const operator <
重载就可以满足要求并得到你想要的:
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 的问题。
注意:我添加了 distanceFrom
和 operator <<
成员和朋友以供以后在最终示例中使用;稍后你会看到它们。
优先级队列实例比较覆盖
以前从未这样做过,但如果有更简单的方法,我当然愿意接受建议。为您的优先级队列使用 template-type 比较器覆盖的问题是您不能真正做到这一点。您希望每个队列根据到原点的距离进行排序,其中原点是该队列的映射键。这意味着每个 comparison 对象必须以某种方式提供地图键的原点,而你不能用 template-type 覆盖来做到这一点(即 compile-time 东西).
但是,您可以 做的是提供一个实例 比较覆盖。 std::priority_queue
允许您提供自定义比较对象 创建队列的地方 (在我们的例子中,当它作为 mapped-to 目标插入地图时)。稍微按摩一下,我们想出了这个:
首先,一个不带参数或节点的优先仿函数。
// 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
将为我们节省大量输入示例的时间。
示例
使用上面的内容,我们现在可以构建我们的地图和队列了。下面创建一个包含 10 个节点的随机列表,每个节点都携带 1..9 范围内的 x,y。然后我们使用这些节点构建十个优先级队列,一个对应于我们正在创建的每个映射条目。地图条目是对角线切片(即(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
播种以具有可重复的序列。当您决定通过 non-repeatable 测试来解决这个问题时,只需将该种子替换为声明旁边注释中提供的种子即可。
输出(当然,你的会有所不同)。
(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 计算的准确性,但希望你明白了。