【问题标题】:Using two objects as hash key for an unordered_map or alternatives使用两个对象作为 unordered_map 或替代品的哈希键
【发布时间】:2015-11-23 14:24:35
【问题描述】:

定义了我的对象 myType,我需要存储这些对象之间的关系。这些关系存储在一个矩阵中。

元素的数量是事先知道的,并不是所有的元素都有关系(element1 可以与 element3 有关系,但可能没有一个与 5 有关系)并且内存是一个问题。例如,它可能看起来像:

element45 与:

  • element3 具有特征 [3,1;1,4]
  • 具有特征 [1,1;1,1] 的元素 12
  • element1780 具有特征 [8,1;1,4]

element1661 与:

  • 具有特征 [3,1;6,4] 的元素 3
  • 具有特征 [1,1;1,9] 的元素 1
  • element1780 具有特征 [8,1;1,1]

拥有:

myType* element1;
myType* element2;

我想要类似的东西(正确指出元素):

my_table[element1][element2][1][2]=7;

我曾想过使用 boost 库创建嵌套哈希表:

boost::unordered_map<myType*, boost::unordered_map<myType*,
                std::vector<std::vector <unsigned short int> > > > my_table;

但是,即使代码编译,它也会崩溃(分段错误,它指向计算哈希键的行)运行一个简单的行,例如:

my_table[element1][element2].resize(2);
for(int t=0; t<2; ++t)
    my_table[element1][element2][t].resize(2);

谁能给我一些关于这个的信息?这在实践上或概念上是错误的吗?

欢迎任何其他解决此问题的方法。

谢谢

【问题讨论】:

  • myType* element1; 嗯,我不认为指针会很好地工作。
  • 如果您有兴趣:livecoding.tv/sehe -- 我将尝试以 Boost Graph 的方式处理此问题(应该这样:))
  • 为什么你认为指针不能很好地工作?我一直在使用带指针的哈希表并且没有问题,我的问题是如何使用它们中的一对作为哈希键

标签: c++ boost hash


【解决方案1】:

在我看来,您的数据结构代表一个图(属性顶点和连接它们的边)似乎很明显。

此外,当您说“这些关系存储在一个矩阵中。”时,您显然是指“我将其可视化为一个矩阵”,因为真正的矩阵表示¹对于更大的数字来说会变得非常节省空间顶点和稀疏边缘覆盖。

Boost 有一个库:Boost Graph Library (BGL)

如果我们假设您希望能够阅读类似² 的图表

graph X {
    element1; element12; element166; element1780; element3; element4; 

    element45   -- element3    [ label="[3,1;1,4]" ];
    element45   -- element12   [ label="[1,1;1,1]" ];
    element45   -- element1780 [ label="[8,1;1,4]" ];

    element1661 -- element1    [ label="[1,1;1,9]" ];
    element1661 -- element3    [ label="[3,1;6,4]" ];
    element1661 -- element1780 [ label="[8,1;1,1]" ];
}

进入 BGL 兼容模型,使用 typedef,例如:

struct Vertex { 
    std::string node_id;
};
struct Edge {
    Box box; 
};

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vertex, Edge>;

现在您可以利用 BGL 的全部功能:

从文件中读取图表

从 graphviz 文件中读取是一项功能³:

std::ifstream ifs("input.txt");
Graph result;

boost::dynamic_properties dp;
dp.property("node_id", boost::get(&Vertex::node_id, result));
dp.property("label",   boost::get(&Edge::box, result));

read_graphviz(ifs, result, dp);

操作图表

许多算法(dijkstra、流、生成树、连接组件等)可供您使用。或者你可以混合搭配。例如让我们过滤掉没有连接的节点:

struct Filter {
    Graph const* _g;

    bool operator()(Graph::vertex_descriptor v) const {
        return boost::size(boost::adjacent_vertices(v, *_g))>0;
    }

    template <typename T>
    bool operator()(T&&) const { return true; /*catch-all*/ }
};

using Filtered = filtered_graph<Graph, Filter, Filter>;
Filter filter { &graph };
Filtered filtered(graph, filter, filter);

让我们再次将其写入graphviz:

boost::dynamic_properties dp;
dp.property("node_id", boost::get(&Vertex::node_id, filtered));
dp.property("label",   boost::get(&Edge::box, filtered));

write_graphviz_dp(std::cout, filtered, dp);

演示时间

完整的演示采用您的输入图:

并将其过滤成:

完整代码

Live On Coliru

// http://stackoverflow.com/questions/32279268/using-two-objects-as-hash-key-for-an-unordered-map-or-alternatives
#include <cassert>
#include <iostream>

template <typename T> struct BasicBox {
    struct Point { T x, y; };

    Point tl, br;

    friend std::ostream& operator<<(std::ostream& os, Point const& p) { return os << p.x << ',' << p.y;                 } 
    friend std::ostream& operator<<(std::ostream& os, BasicBox const& b) { return os << '[' << b.tl << ';' << b.br << ']'; } 

    friend std::istream& operator>>(std::istream& is, Point& p) {
        char comma;

        if (!(is >> p.x >> comma >> p.y) && (comma == ',')) {
            is.setstate(std::ios::failbit | is.rdstate());
        }
        return is;
    } 
    friend std::istream& operator>>(std::istream& is, BasicBox& b) {
        char lbrace, semi, rbrace;
        if (!(
            (is >> lbrace >> b.tl >> semi >> b.br >> rbrace) &&
            (lbrace == '[' && semi == ';' && rbrace == ']')
        )) {
            is.setstate(std::ios::failbit | is.rdstate());
        }
        return is;
    }
};
using Box = BasicBox<int>;

#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graphviz.hpp>
#include <libs/graph/src/read_graphviz_new.cpp>

struct Vertex { 
    std::string node_id;
}; 
struct Edge {
    Box box; 
};

using Graph = boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS, Vertex, Edge>;

#include <fstream>
#include <boost/graph/filtered_graph.hpp>

struct Filter {
    Graph const* _g;

    bool operator()(Graph::vertex_descriptor v) const {
        return boost::size(boost::adjacent_vertices(v, *_g))>0;
    }

    template <typename T>
    bool operator()(T&&) const { return true; /*catch-all*/ }
};

int main() {
    using namespace boost;

    Graph const graph = []{ 
        std::ifstream ifs("input.txt");
        Graph result;

        boost::dynamic_properties dp;
        dp.property("node_id", boost::get(&Vertex::node_id, result));
        dp.property("label",   boost::get(&Edge::box, result));

        read_graphviz(ifs, result, dp);
        return result;
    }();

    // let's do some random task. Like. You know. Like... Filter out the unconnected nodes
    using Filtered = filtered_graph<Graph, Filter, Filter>;
    Filter filter { &graph };
    Filtered filtered(graph, filter, filter);

    boost::dynamic_properties dp;
    dp.property("node_id", boost::get(&Vertex::node_id, filtered));
    dp.property("label",   boost::get(&Edge::box, filtered));

    write_graphviz_dp(std::cout, filtered, dp);
}

¹例如BGL's AdjacencyMatrix

² 选择的格式是 Graphviz 的 DOT 格式:http://www.graphviz.org/

³当然,您也可以将 Boost Serialization 与 BGL 模型一起使用,因此您可以选择更紧凑的二进制表示,例如

【讨论】:

  • 如果您仍然对我如何通过 BGL 蹒跚学步感兴趣:part #1part #2part #3 录制的直播 (experiment)
  • 非常感谢您的详尽回复。元素(此处为节点)之间的关系我使用机器学习算法学习它们,所以我需要访问这些值并“动态”修改它们,这种数据结构是否可行?元素之间的关系不是双向的,所以 element3 到 element5 与元素不同,扩展到双向图是不是很简单?还有一个比我最关心的...实际上我将有大约 10000 个节点,我需要创建大约 300 个图的结构,这可能是内存问题吗?
  • 是的。 adjacency_list 模型 MutableGraph 所以这些操作都支持这个。
  • 双向性是一种选择(因为您没有提到它,除非另有说明,否则英语中的“连接”直观地是双向的)。这是一个有向图版本:Live On Coliru,它还出于演示目的进行了一些随机突变:after.png
  • 关于实际的内存需求,不知道使用模式是无法判断的。一般来说,邻接表对于内存效率来说并不是一个糟糕的选择。您可以使用随机生成的图表运行一些测试。您正在达到数据结构设计的境界,我们需要(大量)更多信息才能在那里思考
【解决方案2】:

您可以将boost::unordered_map 与键std::pair&lt;myType*, myType*&gt; 结合使用boost::hash。您可以将其声明为:

boost::unordered_map<std::pair<myType*, myType*>, 
                     std::vector<std::vector<char>>, 
                     boost::hash<std::pair<myType*, myType*>>> dictionary;

然后你可以像下面的例子一样在字典中加载每一对的特征:

dictionary[std::make_pair(&a, &b)] = std::vector<std::vector<char>>(1, {1, 2, 3, 4, 5});

并以如下方式访问它们:

dictionary[std::make_pair(&a, &b)][0][0];

LIVE DEMO

【讨论】:

  • 那么,这个解决方案只是使用一对数据制作自定义哈希键?如果这可行,我发现它易于实现且优雅,但对内存不太确定
  • @gui 内存呢?
  • 对不起,我错了,与内存无关。顺便说一句,make_pair(&a, &b),那里的内存地址的使用让我很困惑,应该定义哈希表的键是存储在指针上的内存地址,所以make_pair(a,b)应该没问题,不应该?
  • @gui 由std::make_pair返回的std::pair对象中存储的两个地址(即ab的地址)定义的哈希值,而不是通过返回的std::pair 对象本身。
猜你喜欢
  • 2011-03-29
  • 2011-04-17
  • 1970-01-01
  • 2019-04-24
  • 2021-07-19
  • 2013-02-24
  • 1970-01-01
  • 2015-12-30
  • 2019-04-02
相关资源
最近更新 更多