【问题标题】:std::map with std::pair keys where pair elements has no order importance带有 std::pair 键的 std::map ,其中对元素没有顺序重要性
【发布时间】:2013-03-14 21:00:42
【问题描述】:

正如问题所说,我需要以这样的方式使用 std::map。

std::map<std::pair<int, int>, int*> m;

int* a_ptr = new int;
*a_ptr = 15;
m[std::make_pair(1, 2)] = a_ptr;
std::cout << *m[std::make_pair(2, 1)] << std::endl; //should output 15

现在,在我的实际实现中,所有键和值实际上都是指针。我应该如何解决这个问题?

我想到了两个想法。

  1. 一个是我应该写一个函数,每次我使用m[] 访问或写入地图,我也应该m.find()检查 其他对组合并照此行事。

  2. Other 正在使用带有自定义哈希的 std::unordered_map pair 的元素位置被切换时,不知何故没有区别。 (我不知道该怎么做,如果我乘以或添加 两个指针的结果将不相等。如果这是需要一些帮助 要走的路。)

如果您能想到更好的方法,我会很高兴听到它,否则我已经在第二条中说明了我需要帮助的地方。 (我觉得效率更高,第一个不好看)

谢谢。

【问题讨论】:

  • 在使用它们作为键之前将地图包装在一个对对元素进行排序的类中(所以p.first &lt; p.second)?
  • 为什么要标记这个算法?
  • @Knoothe 解决了这个问题。 Yakk,我不敢相信我从来没有想过。
  • @Tolga 您也可以对指针进行异或运算,该操作是可交换的并且不会溢出。 (然而,基于内存中对象的布局,它可能具有可怕的散列行为。)
  • @millimoose 这在我可以预见的其他一些情况下会派上用场。谢谢。

标签: c++ stl


【解决方案1】:

您能否简单地确保这些对始终保持相同的顺序?使用辅助函数,例如:

std::pair<int,int> my_make_pair(int a, int b) 
{
    if ( a < b ) return std::pair<int,int>(a,b);
    else return std::pair<int,int>(b,a);
}

并始终使用它来访问地图:

m[my_make_pair(1,2)] = a_ptr;
std::cout << m[my_make_pair(2, 1)] << std::endl;

【讨论】:

    【解决方案2】:

    只需使用自定义比较器

    [](std::pair<int, int> const& lhs, std::pair<int, int> const& rhs) {
       int lhs_min = std::min(lhs.first, lhs.second);
       int lhs_max = std::max(lhs.first, lhs.second);
       int rhs_min = std::min(rhs.first, rhs.second);
       int rhs_max = std::max(rhs.first, rhs.second);
    
       return lhs_min < rhs_min || (!(rhs_min < lhs_min) && lhs_max < rhs_max)
    }
    

    这是如何工作的?

    前几行确定性地对一对中的两个元素进行排序;也就是说,您可以按任意顺序提供它们,lhs_minlhs_max 将是相同的。然后,我们对结果使用标准等效技术。所有整数副本都将由编译器优化,min/max 将被内联。

    请注意,我使用 C++11 的 lambdas 是为了方便输入,但后来发现没有很好的方法可以将它们用作 std::map 的比较器,因此您必须正确编写函子。

    【讨论】:

    • 你不能那样做。 std::map 需要一个类型作为它的第三个模板参数,你给它一个 lambda,它是一个值。 AFAIK 在这里使用 lambda 作为比较器,您必须使用auto cmp = [] ... ; typedef std::map&lt;..., decltype(cmp)&gt; mymap;,但您仍在创建一个没人使用的全局对象。一开始只能使用仿函数。
    • 另外我认为你指定的类型必须实现函数调用运算符()?
    • @snipes83 不,lambda 本质上是具有时髦构造函数和operator() 的函子。
    【解决方案3】:
    template<typename T, typename PairCmp = std::less<std::pair<T,T>> >
    struct symmetric_pair_sort {
      bool operator()( std::pair<T,T> const& left, std::pair<T,T> const& right ) const {
        if (left.second<left.first) {
          return (*this)(std::make_pair( left.second, left.first ), right );
        }
        if (right.second<right.first) {
          return (*this)(left, std::make_pair( right.second, right.first ) );
        }
        return PairCmp()(left, right);
      }
    };
    
    // or, the far bulkier yet more efficient:
    template<typename T, typename PairCmp = std::less<std::pair<T&, T&>> >
    struct symmetric_pair_sort {
      template<bool left_reversed, bool right_reversed>
      bool Helper( std::pair<T,T> const& left, std::pair<T,T> const& right ) const {
        std::pair<T&, T&> left_ordered( left_reversed?left.first:left.second, left_reversed?left.second:left.first );
        std::pair<T&, T&> right_ordered( right_reversed?right.first:right.second, right_reversed?right.second:right.first );
        return PairCmp()( left_ordered, right_ordered );
      }
      bool operator()( std::pair<T,T> const& left, std::pair<T,T> const& right ) const {
        if (left.second<left.first) {
          if (right.second<right.first) {
            return Helper<true, true>(left, right);
          } else {
            return Helper<true, false>(left, right);
          }
        } else {
          if (right.second<right.first) {
            return Helper<false, true>(left, right);
          } else {
            return Helper<false, false>(left, right);
          }
      }
    };
    
    std::map<std::pair<int, int>, int*, symmetric_pair_sort<int> > m;
    

    【讨论】:

    • 一个不错的解决方案。这在其他一些场景中会派上用场,但如果订单失败,它将创建另一个 pair 对象,而 Nate Hekman 将创建一个。我没有太多可以访问地图的地方,所以现在将 make_pairs 转换为 my_make_pairs 就可以了。否则这就是答案。
    • -1 我不喜欢对我也回答过的问题投反对票,但我不喜欢这里的递归调用;编译器是否能够删除它们并不明显。不幸的是,要使用自定义的 PairCmp,必须创建一个新的对,但为什么不直接将其传递给 PairCmp
    • 4折盒看起来很丑。 :) 会更快,是的。
    • 我写了一个更快的,其中PairCmp 预计会采用std::pair&lt;T&amp;, T&amp;&gt; 而不是`std::pair。丑陋得多,但对于非平凡类型可能更快:对于简单类型,我相信编译器可以在简单情况下找出递归。
    【解决方案4】:

    另一种解决方案是使用std::set&lt;int&gt; 而不是std::pair&lt;int,int&gt;。 Sets 存储有序的数据,因此无需订购您的配对,因此准确地满足您的要求。

    另一方面,使用明确排序对的比较包装器的其他答案可能更实用。

    【讨论】:

    • 怎么样?如果我有 set&lt;int&gt; a, b 并在 a 中插入 1,2 并在 b 中以相反的顺序插入,则 a==b 为真。
    • 是的,当然。它们具有相同的内容。但是,std::set 是有序结构;也就是说,它以有序的方式存储其内容。
    • 是的,当然,正因为如此,它们满足了开箱即用的要求:这两个集合比较相等,因此 {1,2} 的查找也将匹配键{2,1}(请注意,它们在数学上完全相等)。
    • 我反对std::set 是一个有序的容器,不是因为如果你以任意方式插入它们,它们就不会比较相等(它们不会,它们比较相等)。但是,std::maps 不在乎他们的密钥是否相等。他们只关心严格的弱偏序,通常由std::lessoperator&lt; 提供。当应用于 C++ 时,它会起作用,但不是因为你所说的原因。 (我在 Uni 学过数学,我知道集合 - 在数学意义上 - 是什么。)
    • @Alex:好的,感谢您的澄清,我更新了我的答案。我混淆了集合的数学定义和这些的 STL 实现。事实上,我忽略了一个事实,即为了实现,必须存在排序(尽管这与它们在这里的使用无关)。
    猜你喜欢
    • 2015-03-04
    • 1970-01-01
    • 1970-01-01
    • 2022-10-24
    • 2011-03-24
    • 2020-08-27
    • 1970-01-01
    • 2021-12-23
    • 1970-01-01
    相关资源
    最近更新 更多