【问题标题】:Matching Map without Recreating匹配地图而不重新创建
【发布时间】:2015-11-25 17:23:18
【问题描述】:

假设我有两个maps。这些maps 的值是相同的,并且构造起来很昂贵(而不是复制)。这些maps 的键是不同类型的,但可以相互转换。我需要设置第一个map 的内容以匹配第二个的内容,但我必须一直循环通过两个maps。有没有办法做到这一点?

作为一个例子,我将键简化为更易于识别的可转换对象,并仅使用 ints 作为值。在示例中,我想设置foo 的内容以匹配bar 的内容。但是如果不循环遍历maps,我就找不到办法。

map<int, int> foo = {{1, 100}, {2, 200}, {4, 400}};
map<char, int> bar = {{'1', 200}, {'3', 300}, {'5', 500}};

for(auto i = foo.begin(); i != foo.end(); ++i) {
    if(bar.end() == bar.find(static_cast<decltype(bar)::key_type>(i->first) + '0')){
        foo.erase(i);
    }
}

for(auto i = bar.begin(); i != bar.end(); ++i) {
    const decltype(foo)::key_type key = i->first - '0';

    if(foo.end() == foo.find(key) || foo[key] != i->second) {
        foo[key] = i->second;
    }
}

for(const auto i : foo){
    cout << i.first + 10 << ": " << i.second << endl;
}

这正确输出:

11:200
13:300
15:500

[Live Example]

有没有一种不需要循环遍历maps 的方法?

【问题讨论】:

  • 键的顺序是否相同?
  • 在这种情况下,您可以通过并行循环两个地图并合并来更快地获得相同的结果。这避免了bar.find() 调用,但您仍然需要迭代这两个映射。我不知道有什么办法。
  • 如果钥匙是可转换的,为什么你需要两个?
  • @GlennTeitelbaum 这是我正在设计的一个不幸的人工制品。事实上我不需要两者,如果我被允许我会删除一个。然而,此时我一直在做转换,所以我至少想把它做对。
  • @rici 我认为您的回答与我将要做的一样好。如果您想发帖,请不要这样做,否则我可以写出来。

标签: c++ dictionary key assignment-operator


【解决方案1】:

如果不检查每个集合的每个元素,就无法同步两个集合。所以你将不得不遍历这两个集合。

但是如果集合以相同的顺序键入,您可以通过并行迭代两个集合并合并来加快速度。如果您的标准库有一个有用的emplace_hint 实现,这将特别有效。

基本伪代码(意味着它不会编译并且可能无法正常工作:-))。

/* Makes a into a copy of b, assuming that the key types are
 * consistently comparable, and that a key for a can be constructed
 * from a key for b.
 */
void merge(Map1& a, Map2& b) {
  auto it_a = a.begin(), end_a = a.end();
  auto it_b = b.begin(), end_b = b.end();
  for (;;) {
    if (it_a == end_a) {
      /* Add remaining elements from b to a */
      for (; it_b != end_b; ++it_b)
        a.emplace_hint(it_a, it_b->first, it_b->second);
      return;
    } else if (it_b == end_b) {
      /* Remove remaining elements from a */
      while (it_a != end_a)
        it_a = a.erase(it_a);
      return;
    } else if (it_b->first < it_a->first) {
      /* Insert an element from b */
      a.emplace_hint(it_a, it_b->first, it_b->second);
      ++it_b;
    } else if (it_b->first == it_a->first) {
      /* Replace an element from b */
      a->second = b->second;
      ++it_a, ++it_b;
    } else {
      /* Delete element from a */
      it_a = a.erase(it_a);
    }
  }
}

注意: 上面的代码注意不要不必要地构造一个新值,如果它可以覆盖现有值,但它并没有真正避免构造值,因为它可能会破坏与a 中不需要的键,然后构造与 a 中不存在的键关联的值。如果复制构造比赋值昂贵得多,那么保留一个构造值池可能是有意义的,但代价是向映射值添加间接。 shared_ptr 是另一种可能性,尽管它也可能是矫枉过正。

【讨论】:

  • 因为我的特殊问题已经为maps 订购了密钥,这对我来说是最好的解决方案,所以我接受它。不过,在可以访问 Boost 的一般情况下,Barry's solution 可能是首选。
【解决方案2】:

使用 boost transformed 适配器,您可以简单地使用带有两个迭代器的 std::map 构造函数,并以更不容易出错、更直接的方式实现相同的目标:

using namespace boost::adaptors;
auto transformed_map = 
    bar | transformed([](const std::pair<const char, int>& p) {
        return std::make_pair(p.first - '0', p.second);
    });

foo = std::map<int, int>(std::begin(transformed_map),
                         std::end(transformed_map));

我发现上面的内容更容易理解。

现在,如果您发现自己经常这样做,而且建造成本太高,这可能表明存在更大的设计问题。也许您想存储shared_ptr&lt;value_type&gt; 而不是value_type。或者也许你只是想做一些类似的事情:

struct two_maps {
    std::map<int, int> foo;

    auto from_foo(int k) { return foo.find(k); }
    auto from_bar(char k) { return foo.find(k - '0'); }
};

【讨论】:

  • 有趣的想法。不过,我无权为此使用 Boost,因此该解决方案不会让我浮起来。
猜你喜欢
  • 2023-03-25
  • 2013-11-08
  • 2012-08-25
  • 1970-01-01
  • 2021-10-25
  • 1970-01-01
  • 1970-01-01
  • 2021-05-20
  • 2016-07-31
相关资源
最近更新 更多