【问题标题】:What is the best way to use two keys with a std::map?将两个键与 std::map 一起使用的最佳方法是什么?
【发布时间】:2010-11-09 21:31:56
【问题描述】:

我有一个std::map,我用它来存储 x 和 y 坐标的值。我的数据非常稀疏,所以我不想使用数组或向量,这会导致大量内存浪费。我的数据范围从-250000到250000,但我最多只有几千个点。

目前我正在创建一个带有两个坐标的std::string(即"12x45")并将其用作键。这似乎不是最好的方法。

我的其他想法是使用 int64 并将两个 int32 塞入其中并用作键。

或者使用具有两个坐标的类。对用作 key 的类有什么要求?

最好的方法是什么?我宁愿不使用地图。

【问题讨论】:

  • 您可以轻松合法地将两个 long 填充到一个 _int64 中,或者如下面的回答,一个序列号、PID 和 NodeId。由于 MAX_PID 在 Linux 上为 (1 的任何值
  • 假设您不想按特定顺序迭代映射,请使用像 std::unordered_map 这样的哈希映射。效率更高,尤其是当您拥有这么多值时。

标签: c++ dictionary stl key stdmap


【解决方案1】:

使用 std::pair 作为密钥:

std::map<std::pair<int,int>, int> myMap;

myMap[std::make_pair(10,20)] = 25;
std::cout << myMap[std::make_pair(10,20)] << std::endl;

【讨论】:

  • 一对有效但通用。定义一个自定义类型来表达这对的真正含义可能是个好主意,笛卡尔坐标或其他。
  • @bames53 主要问题是您必须为新类型实现一个比较运算符,如果您只想确保唯一性,这可能没有意义。
  • 直到现在才知道 std::pair 的比较运算符有重载。
  • @KyleStrand 这是怎么回事?比较运算符不会违反唯一性。此外,您可能只将比较运算符定义为map 的第三个参数,而不是类型本身。说代码,因此代码清晰肯定支持 bames53 的建议
  • @IceFire 我想我想说的是,您不想定义一个运算符,该运算符暗示对不应该支持排序语义概念的类型进行逻辑排序,因为它可能被滥用。但显然使用pair 并不能真正避免这个问题。我认为您将运算符作为map 参数提供的建议是正确的解决方案。
【解决方案2】:

我通常这样解决这类问题:

struct Point {
    int x;
    int y;
};

inline bool operator<(const Point& p1, const Point& p2) {
    if (p1.x != p2.x) {
        return p1.x < p2.x;
    } else {
        return p1.y < p2.y;
    }
}

【讨论】:

  • 这是明确的,这很好。请注意,这与typedef std::pair&lt;int, int&gt; Point; 相同
  • 嘿,直到现在我还不知道 std::pair 定义了 operator
  • @GManNickG 除了xy 的接口是firstsecondstd::pair
  • 这非常好,因为也可以用于包含超过 2 个字段的键。
  • 您还可以添加一个比较函子作为 map 的模板参数:std::map&lt;Point, std::string, PointComp&gt; mapping;
【解决方案3】:

Boost 有一个使用一个或多个索引的地图容器。

Multi Index Map

【讨论】:

  • 也可以通过boost的元组来实现。
【解决方案4】:

对用作键的类有什么要求?

映射需要能够判断一个键的值是否小于另一个键的值:默认情况下,这意味着 (key1

map 模板还实现了一个重载的构造函数,它允许您传入对 key_compare 类型的函数对象的引用,该对象可以实现比较运算符:因此,或者可以将比较实现为这个外部函数对象的方法,而不是需要被烘焙到您的密钥的任何类型。

【讨论】:

  • 另外一个要求是key必须是不变的,不能改变
【解决方案5】:

这会将多个整数键填充到一个大整数中,在本例中为 _int64。它比较为 _int64,AKA long long(有史以来最丑陋的类型声明。short short short short,只会稍微不那么优雅。10 年前它被称为 vlong。好多了。“进步”这么多),所以没有比较需要功能。

#define ULNG  unsigned long
#define BYTE  unsigned char
#define LLNG  long long 
#define ULLNG unsigned long long

// --------------------------------------------------------------------------
ULLNG PackGUID(ULNG SN,  ULNG PID, BYTE NodeId) {
    ULLNG CompKey=0;

    PID = (PID << 8) + NodeId;
    CompKey = ((ULLNG)CallSN << 32) + PID;

    return CompKey;
}

提供了这个答案后,我怀疑这对你有用,因为你需要两个独立且不同的键来在二维 X 和 Y 中导航。

另一方面,如果您已经有了 XY 坐标,并且只想将值与该键相关联,那么这非常有效,因为 _int64 比较与 Intel X86 芯片上的任何其他整数比较所花费的时间相同 - 1个时钟。

在这种情况下,此合成密钥的比较速度是三重复合密钥的 3 倍。

如果使用它来创建一个人口稀少的电子表格,我将使用 2 棵不同的树进行 RX,其中一棵嵌套在另一棵内。将 Y 维设为“boss”,首先搜索 Y 空间以进行解析,然后再进行 X 维。电子表格的高度大于宽度,并且您总是希望任何复合键中的第一个维度具有最大数量的唯一值。

这种安排将为 Y 维度创建一个映射,该映射将具有 X 维度的映射作为它的数据。当您到达 Y 维度中的叶子时,您开始在其 X 维度中搜索电子表格中的列。

如果您想创建一个非常强大的电子表格系统,请以相同的方式添加 Z 维度,并将其用作组织单位的示例。这是一个非常强大的预算/预测/会计系统的基础,它允许管理单位有很多血淋淋的详细帐户来跟踪管理费用等,并且这些帐户不会占用有自己类型的行单位的空间要跟踪的详细信息。

【讨论】:

  • 我相信将两个 long 填充到一个 _int64 中,假设高 long 是 Y,低 long 是 X,将首先搜索 Y 空间,然后是 X 空间,因为所有具有相同高 long (相同的 Y 值)将相等,留下低长(X 空间)来打破平局。
【解决方案6】:

我认为对于您的用例,std::pair,正如David Norman's answer 中所建议的那样,是最好的解决方案。但是,由于C++11,您也可以使用std::tuple。如果您有两个以上的键,则元组很有用,例如,如果您有 3D 坐标(即xyz)。那么您不必nest pairsdefine a comparator for a struct。但是对于您的特定用例,代码可以编写如下:

int main() {
    using tup_t = std::tuple<int, int>;
    std::map<tup_t, int> m;

    m[std::make_tuple(78, 26)] = 476;
    tup_t t = { 12, 45 }; m[t] = 102;

    for (auto const &kv : m)
        std::cout << "{ " << std::get<0>(kv.first) << ", "
                          << std::get<1>(kv.first) << " } => " << kv.second << std::endl;
    return 0;
}

输出:

{ 12, 45 } => 102
{ 78, 26 } => 476

注意:由于C++17 使用元组变得更加容易,尤其是如果您想同时访问多个元素。 例如,如果使用structured binding,则可以按如下方式打印元组:

for (auto const &[k, v] : m) {
    auto [x, y] = k;
    std::cout << "{ " << x << ", " << y << " } => " << v << std::endl;
}

Code on Coliru

【讨论】:

    【解决方案7】:

    使用 std::pair。如果你有很多这样的映射,最好使用QHash&lt;QPair&lt;int,int&gt;,int&gt;

    【讨论】:

      【解决方案8】:

      希望你会发现它有用:

      map<int, map<int, int>> troyka = { {4, {{5,6}} } };
      troyka[4][5] = 7;
      

      【讨论】:

      • OP 说他宁愿不使用地图地图,所以这个答案不太可能有帮助。此外,对代码的一些解释可能有助于其他人理解您的解决方案。抱歉,如果我抱怨太多,但是您已经回答了 2009 年的一个非常古老的问题,该问题已经得到了很多赞成。因此,您的答案不太可能提供比公认答案新的甚至更好的东西。
      【解决方案9】:

      顶级结果的替代方案,性能稍差,但更容易索引

      std::map<int, std::map<int,int>> myMap;
      
      myMap[10][20] = 25;
      std::cout << myMap[10][20] << std::endl;
      

      【讨论】:

      • OP 特别提到他们不想使用地图。
      【解决方案10】:

      首先,放弃字符串并使用 2 个整数,您现在可能已经完成了。感谢您发现树是实现稀疏矩阵的最佳方式。通常它似乎是一个糟糕的实现的磁铁。

      仅供参考,三重复合键也可以,我也假设一对。

      虽然它会产生一些丑陋的子脚本,所以一点宏魔法会让你的生活更轻松。我保留了这个通用目的,但是如果您为特定映射创建宏,则在宏中类型转换参数是一个好主意。 TresKey12 已经过测试并且运行良好。 QuadKeys 也应该可以工作。

      注意:只要您的关键部分是基本数据类型,您就无需再编写任何内容。 AKA,无需担心比较功能。 STL 涵盖了您。只需将其编码并让它撕裂。

      using namespace std;    // save some typing
      #define DosKeys(x,y)      std::make_pair(std::make_pair(x,y))
      #define TresKeys12(x,y,z) std::make_pair(x,std::make_pair(y,z))
      #define TresKeys21(x,y,z) std::make_pair(std::make_pair(x,y),z))
      
      #define QuadKeys(w,x,y,z) std::make_pair(std::make_pair(w,x),std::make_pair(y,z))
      
      
      map<pair<INT, pair<ULLNG, ULLNG>>, pIC_MESSAGE> MapMe;
      MapMe[TresKey12(Part1, Part2, Part3)] = new fooObject;
      

      如果有人想给我留下深刻印象,请告诉我如何为 TresKeys 创建一个不依赖嵌套对的比较运算符,以便我可以使用具有 3 个成员的单个 struct 并使用比较函数。

      PS: TresKey12 给我一个声明为 pair,z 的地图的问题,因为它生成 x,pair,而这两个并不好。 DosKeys 或 QuadKeys 不是问题。如果周五是炎热的夏天,您可能会发现在 DosEquis 中打字会产生意想不到的副作用 ... err.. DosKeys 很多次,是对墨西哥啤酒的渴望。买者自负。正如 Sheldon Cooper 所说,“没有奇思妙想的生活是什么?”。

      【讨论】:

      • 这很难看。为什么应该是函数的宏?这是太多的嵌套对。要回答您有关如何解决的问题:struct location { int x, y, z; }; bool operator&lt;(const location&amp; lhs, const location&amp; rhs) { return std::tie(lhs.x, lhs.y, lhs.z) &lt; std::tie(rhs.x, rhs.y, rhs.z); }
      • @GManNickG,感谢您的回复。会试一试并报告。是的,如果这行得通,这家伙会笑的!
      • @GManNickG,这并不完全编译。我将在 VS 2012 C++ 中使用它。可能让它工作,但如果你有时间,也许发布一些正在编译的东西? TVMIA
      • 为我工作:coliru.stacked-crooked.com/…,以及在 VS2012 中。
      • @GManNickG,给我留下了深刻的印象,感谢您在这里加倍努力。在这里面对精益敏捷部分,因此感谢您的帮助。
      猜你喜欢
      • 2012-04-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-12-23
      • 2010-10-16
      • 2011-04-11
      相关资源
      最近更新 更多