当前位置: 首页 > 知识库问答 >
问题:

使用自定义类类型作为键的C无序_映射

沈嘉瑞
2023-03-14

我试图使用一个自定义类作为无序的_映射的键,如下所示:

#include <iostream>
#include <algorithm>
#include <unordered_map>

using namespace std;

class node;
class Solution;

class Node {
public:
    int a;
    int b; 
    int c;
    Node(){}
    Node(vector<int> v) {
        sort(v.begin(), v.end());
        a = v[0];       
        b = v[1];       
        c = v[2];       
    }

    bool operator==(Node i) {
        if ( i.a==this->a && i.b==this->b &&i.c==this->c ) {
            return true;
        } else {
            return false;
        }
    }
};

int main() {
    unordered_map<Node, int> m;    

    vector<int> v;
    v.push_back(3);
    v.push_back(8);
    v.push_back(9);
    Node n(v);

    m[n] = 0;

    return 0;
}

但是,g给出了以下错误:

In file included from /usr/include/c++/4.6/string:50:0,
                 from /usr/include/c++/4.6/bits/locale_classes.h:42,
                 from /usr/include/c++/4.6/bits/ios_base.h:43,
                 from /usr/include/c++/4.6/ios:43,
                 from /usr/include/c++/4.6/ostream:40,
                 from /usr/include/c++/4.6/iostream:40,
                 from 3sum.cpp:4:
/usr/include/c++/4.6/bits/stl_function.h: In member function ‘bool std::equal_to<_Tp>::operator()(const _Tp&, const _Tp&) const [with _Tp = Node]’:
/usr/include/c++/4.6/bits/hashtable_policy.h:768:48:   instantiated from ‘bool std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_M_compare(const _Key&, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type, std::__detail::_Hash_node<_Value, false>*) const [with _Key = Node, _Value = std::pair<const Node, int>, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, std::__detail::_Hash_code_base<_Key, _Value, _ExtractKey, _Equal, _H1, _H2, std::__detail::_Default_ranged_hash, false>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable.h:897:2:   instantiated from ‘std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node* std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_M_find_node(std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node*, const key_type&, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type) const [with _Key = Node, _Value = std::pair<const Node, int>, _Allocator = std::allocator<std::pair<const Node, int> >, _ExtractKey = std::_Select1st<std::pair<const Node, int> >, _Equal = std::equal_to<Node>, _H1 = std::hash<Node>, _H2 = std::__detail::_Mod_range_hashing, _Hash = std::__detail::_Default_ranged_hash, _RehashPolicy = std::__detail::_Prime_rehash_policy, bool __cache_hash_code = false, bool __constant_iterators = false, bool __unique_keys = true, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Node = std::__detail::_Hash_node<std::pair<const Node, int>, false>, std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::key_type = Node, typename std::_Hashtable<_Key, _Value, _Allocator, _ExtractKey, _Equal, _H1, _H2, _Hash, _RehashPolicy, __cache_hash_code, __constant_iterators, __unique_keys>::_Hash_code_type = long unsigned int]’
/usr/include/c++/4.6/bits/hashtable_policy.h:546:53:   instantiated from ‘std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type& std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::operator[](const _Key&) [with _Key = Node, _Pair = std::pair<const Node, int>, _Hashtable = std::_Hashtable<Node, std::pair<const Node, int>, std::allocator<std::pair<const Node, int> >, std::_Select1st<std::pair<const Node, int> >, std::equal_to<Node>, std::hash<Node>, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, false, false, true>, std::__detail::_Map_base<_Key, _Pair, std::_Select1st<_Pair>, true, _Hashtable>::mapped_type = int]’
3sum.cpp:149:5:   instantiated from here
/usr/include/c++/4.6/bits/stl_function.h:209:23: error: passing ‘const Node’ as ‘this’ argument of ‘bool Node::operator==(Node)’ discards qualifiers [-fpermissive]
make: *** [threeSum] Error 1

我想,我需要告诉C如何散列类节点,然而,我不太确定如何做。我怎样才能完成这些任务?

共有3个答案

范京
2023-03-14

使用自定义类作为unordered_map(稀疏矩阵的基本实现)的键的最基本的可能复制/粘贴完整的可运行示例:

// UnorderedMapObjectAsKey.cpp

#include <iostream>
#include <vector>
#include <unordered_map>

struct Pos
{
  int row;
  int col;

  Pos() { }
  Pos(int row, int col)
  {
    this->row = row;
    this->col = col;
  }

  bool operator==(const Pos& otherPos) const
  {
    if (this->row == otherPos.row && this->col == otherPos.col) return true;
    else return false;
  }

  struct HashFunction
  {
    size_t operator()(const Pos& pos) const
    {
      size_t rowHash = std::hash<int>()(pos.row);
      size_t colHash = std::hash<int>()(pos.col) << 1;
      return rowHash ^ colHash;
    }
  };
};

int main(void)
{
  std::unordered_map<Pos, int, Pos::HashFunction> umap;

  // at row 1, col 2, set value to 5
  umap[Pos(1, 2)] = 5;

  // at row 3, col 4, set value to 10
  umap[Pos(3, 4)] = 10;

  // print the umap
  std::cout << "\n";
  for (auto& element : umap)
  {
    std::cout << "( " << element.first.row << ", " << element.first.col << " ) = " << element.second << "\n";
  }
  std::cout << "\n";

  return 0;
}
赵雅懿
2023-03-14

我认为,jogoJapan给出了一个非常好和详尽的答案。在阅读我的帖子之前,你绝对应该看看它。然而,我想补充以下内容:

  1. 您可以单独为unordered_map定义比较函数,而不是使用相等比较运算符(运算符==)。这可能会很有帮助,例如,如果您想使用后者来比较两个Node对象的所有成员,但仅将一些特定成员作为unordered_map的键。
  2. 您还可以使用lambda表达式来代替定义哈希和比较函数。

总而言之,对于您的Node类,代码可以编写如下:

using h = std::hash<int>;
auto hash = [](const Node& n){return ((17 * 31 + h()(n.a)) * 31 + h()(n.b)) * 31 + h()(n.c);};
auto equal = [](const Node& l, const Node& r){return l.a == r.a && l.b == r.b && l.c == r.c;};
std::unordered_map<Node, int, decltype(hash), decltype(equal)> m(8, hash, equal);

笔记:

  • 我只是在jogojapan的答案末尾重复使用了散列方法,但是你可以在这里找到一个更通用的解决方案(如果你不想使用Boost的话)
危晨
2023-03-14

要能够将std::unordered_map(或其他无序关联容器之一)与用户定义的键类型一起使用,您需要定义两件事:

>

  • 散列函数;这必须是一个覆盖运算符()并计算给定键类型对象的哈希值的类。一种特别直接的方法是为您的键类型专门化std::哈希模板。

    相等的比较函数;这是必需的,因为散列不能依赖于散列函数总是为每个不同的键提供唯一的散列值这一事实(即,它需要能够处理冲突),因此它需要一种方法来比较两个给定的键以获得精确匹配。您可以将其实现为覆盖运算符()的类,或者实现为std::equal的特化,或者最简单的方法是为您的键类型重载运算符==()(正如您已经做的那样)。

    哈希函数的困难在于,如果密钥类型由多个成员组成,通常会让哈希函数为各个成员计算哈希值,然后以某种方式将它们组合成整个对象的一个哈希值。为了获得良好的性能(即很少发生冲突),您应该仔细考虑如何组合各个散列值,以确保避免对不同的对象太频繁地获得相同的输出。

    哈希函数的一个相当好的起点是使用位移位和按位异或来组合各个哈希值。例如,假设密钥类型如下:

    struct Key
    {
      std::string first;
      std::string second;
      int         third;
    
      bool operator==(const Key &other) const
      { return (first == other.first
                && second == other.second
                && third == other.third);
      }
    };
    

    这是一个简单的哈希函数(改编自用户定义哈希函数的cp首选项示例中使用的函数):

    namespace std {
    
      template <>
      struct hash<Key>
      {
        std::size_t operator()(const Key& k) const
        {
          using std::size_t;
          using std::hash;
          using std::string;
    
          // Compute individual hash values for first,
          // second and third and combine them using XOR
          // and bit shifting:
    
          return ((hash<string>()(k.first)
                   ^ (hash<string>()(k.second) << 1)) >> 1)
                   ^ (hash<int>()(k.third) << 1);
        }
      };
    
    }
    

    有了这个,您可以实例化键类型的std::unordered_map

    int main()
    {
      std::unordered_map<Key,std::string> m6 = {
        { {"John", "Doe", 12}, "example"},
        { {"Mary", "Sue", 21}, "another"}
      };
    }
    

    它会自动使用std::哈希

    如果不想在std名称空间内专门化模板(尽管在这种情况下完全合法),可以将哈希函数定义为一个单独的类,并将其添加到映射的模板参数列表中:

    struct KeyHasher
    {
      std::size_t operator()(const Key& k) const
      {
        using std::size_t;
        using std::hash;
        using std::string;
    
        return ((hash<string>()(k.first)
                 ^ (hash<string>()(k.second) << 1)) >> 1)
                 ^ (hash<int>()(k.third) << 1);
      }
    };
    
    int main()
    {
      std::unordered_map<Key,std::string,KeyHasher> m6 = {
        { {"John", "Doe", 12}, "example"},
        { {"Mary", "Sue", 21}, "another"}
      };
    }
    

    如何定义更好的散列函数?如上所述,定义一个好的散列函数对于避免冲突和获得良好性能非常重要。对于真正好的结果,您需要考虑所有字段的可能值的分布,并定义一个哈希函数,该函数将该分布投影到可能结果的空间中,使其尽可能宽且均匀分布。

    这可能很困难;上面的XOR/位移位方法可能是一个不错的开始。为了更好的开始,您可以使用Boost库中的hash_valuehash_combine函数模板。前者的作用类似于标准类型的std::hash(最近还包括元组和其他有用的标准类型);后者可以帮助您将单个哈希值合并为一个。以下是使用Boost助手函数的哈希函数的重写:

    #include <boost/functional/hash.hpp>
    
    struct KeyHasher
    {
      std::size_t operator()(const Key& k) const
      {
          using boost::hash_value;
          using boost::hash_combine;
    
          // Start with a hash value of 0    .
          std::size_t seed = 0;
    
          // Modify 'seed' by XORing and bit-shifting in
          // one member of 'Key' after the other:
          hash_combine(seed,hash_value(k.first));
          hash_combine(seed,hash_value(k.second));
          hash_combine(seed,hash_value(k.third));
    
          // Return the result.
          return seed;
      }
    };
    

    这里有一个重写,它不使用boost,但使用了很好的哈希组合方法:

    namespace std
    {
        template <>
        struct hash<Key>
        {
            size_t operator()( const Key& k ) const
            {
                // Compute individual hash values for first, second and third
                // http://stackoverflow.com/a/1646913/126995
                size_t res = 17;
                res = res * 31 + hash<string>()( k.first );
                res = res * 31 + hash<string>()( k.second );
                res = res * 31 + hash<int>()( k.third );
                return res;
            }
        };
    }
    

  •  类似资料:
    • 我想使用结构作为关键unordered_map我实现了散列和相等操作的结构。但是我有一个错误 hashtable_策略。h:1384:16:错误:对“(const ItemHash)(const Item)的调用不匹配 我怎样才能纠正错误? 我的密码

    • 问题内容: 我必须怎么做才能将自定义类型的对象用作Python字典中的键(我不希望“对象id”用作键),例如 如果名称和位置相同,我想将MyThing用作相同的键。从C#/ Java开始,我习惯于重写并提供一个equals和hashcode方法,并保证不会突变该hashcode依赖的任何内容。 我必须在Python中做什么才能完成此任务?我应该吗? (在一个简单的例子中,就像这里一样,也许最好将一

    • 问题内容: 我认为我的问题很简单,但是我找不到解决方案,所以我决定在这里提问。我需要做的是使用这样的自定义键类型: 但是,我在这里丢失了一些东西,因为停止功能正常。首先,密钥不是唯一的,并且可以在中找到具有相同值的Pair的不同实例。同样,包含键功能不能像我想象的那样起作用:)。 我显然错过了一些东西,并且更有可能应该以某种方式定义一种比较类中实例的方法。但是我在课堂上尝试实现Comparable

    • 问题内容: 我想将非spring bean类对象用作球衣Web服务类方法的参数。但是它在构建时会给出缺少的依赖项错误。 我的代码是: 问题答案: 关键是路径参数以字符串形式出现。根据规范,如果我们希望将自定义类型作为注入,则自定义类应具有以下三项之一: 返回类型的公共静态 返回类型的公共静态 或接受字符串的公共构造函数 另一种选择实现。您可以在此处查看示例。 如果您不拥有该类(它是无法更改的第三方

    • Rust 自定义数据类型主要是通过下面这两个关键字来创建: struct: 定义一个结构体 enum: 定义一个枚举类型 而常量的创建可以通过 const 和 static 关键字来创建。

    • 存在多种方法来重新定义现有类型的行为以及提供新的类型。 重写类型编译 一个常见的需求是强制更改类型的“字符串”版本,即在create table语句或其他SQL函数(如cast)中呈现的版本。例如,应用程序可能希望强制呈现 BINARY 适用于除一个平台外的所有平台 BLOB 待渲染。在本例中,使用现有的泛型类型 LargeBinary ,是大多数用例的首选。但是为了更准确地控制类型,每个方言的编