【问题标题】:How can I construct one map from another map?如何从另一张地图构建一张地图?
【发布时间】:2019-01-05 21:18:52
【问题描述】:

我正在尝试使用比较器函数从另一个映射创建映射,该比较器函数的键值对中的新值与存储在映射中的键值对中的先前值不同。

编译以下代码时出现编译错误。该代码有什么问题?还有更好的方法来实现这一点吗?

#include <iostream>
#include <map>
#include <set>
#include <algorithm>
#include <functional>
int main() {
    // Creating & Initializing a map of String & Ints
    std::map<std::string, int> mapOfWordCount = { { "aaa", 10 }, { "ddd", 41 },
            { "bbb", 62 }, { "ccc", 10} };
    // Declaring the type of Predicate that accepts 2 pairs and return a bool
    typedef std::function<bool(std::pair<std::string, int>, std::pair<std::string, int>)> Comparator;
    // Defining a lambda function to compare two pairs. It will compare two pairs using second field
    Comparator compFunctor =
            [](std::pair<std::string, int> elem1 ,std::pair<std::string, int> elem2)
            {
                return elem1.second != elem2.second;
            };
    // Declaring a set that will store the pairs using above comparision logic
    std::map<std::string, int, Comparator> setOfWords(
            mapOfWordCount.begin(), mapOfWordCount.end(), compFunctor);

    return 0;
}

第二张地图的预期输出是:

{ "aaa", 10 }
{ "ddd", 41 }
{ "bbb", 62 }

这意味着,{ "ccc", 10 } 必须被忽略。

错误摘录:

sortMap.cpp:25:70:从这里需要 /opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/bits/stl_tree.h:1422:8: 错误:调用不匹配 '(std::function, int>, std::pair, int>)>) (const std::basic_string&, const key_type&)’ && _M_impl._M_key_compare(_S_key(_M_rightmost()), __k)) ^ 在 /opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/bits/stl_algo.h:66:0 中包含的文件中, 来自/opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/algorithm:62, 来自 sortMap.cpp:4:/opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/functional:2174:11: 注:候选人是: 类函数<_res> ^ /opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/functional:2466:5: 注意:_Res std::function<_res ...>::operator()(_ArgTypes ...) const [with _Res = bool; _ArgTypes = {标准::对, std::allocator >, int>, std::pair, std::allocator >, int>}] 函数<_res>:: ^

【问题讨论】:

  • std::map 需要一个谓词来对键进行排序。您的 compFunctor 与所需谓词的类型不匹配。 (这可能是编译错误的原因。)但即使修复了这个问题,您也会遇到运行时问题,因为缺少对映射键的充分排序。
  • 在您暴露错误文本后,请删除我上述评论的“可能”。 ;-)
  • 能否请您描述一下您打算如何处理第二张地图?
  • 基本上是假设您收到 Key = 3 和 Value = 4,这将被插入到地图中。现在,当我应该添加 Key = 5 和 Value = 4 时,不应将其插入到地图中。现在再次如果我收到 Key = 5 和 Value = 6,地图应该包含 {3,4} 和 {5,6}
  • 假设你想对第一个映射的条目进行排序,你需要一个std::map&lt;int, string&gt;。进一步假设,第一张地图的值是例如单词的出现,值可能不是唯一的。所以,std::multimap 会更合适(或std::map&lt;int, std::vector&lt;std::string&gt; &gt;)。

标签: c++ dictionary stl


【解决方案1】:

这是根据OP描述的意图的解决方案。

示例代码:

#include <iostream>
#include <map>
#include <set>
#include <vector>

int main()
{
  // Creating & Initializing a map of String & Ints
  std::map<std::string, int> mapOfWordCount = {
    { "aaa", 10 }, { "ddd", 41 }, { "bbb", 62 }, { "ccc", 10 }
  };
  // auxiliary set of values
  std::set<int> counts;
  // creating a filtered map
  std::vector<std::pair<std::string, int> > mapOfWordCountFiltered;
  for (const std::map<std::string, int>::value_type &entry : mapOfWordCount) {
    if (!counts.insert(entry.second).second) continue; // skip duplicate counts
    mapOfWordCountFiltered.push_back(entry);
  }
  // output
  for (const std::pair<std::string, int> &entry : mapOfWordCountFiltered) {
    std::cout << "{ \"" << entry.first << "\", " << entry.second << " }\n";
  }
  // done
  return 0;
}

输出:

{ "aaa", 10 }
{ "bbb", 62 }
{ "ddd", 41 }

Live Demo on coliru

没有自定义谓词用作标准谓词 (std::less&lt;Key&gt;) 足以解决解决方案(map 以及 set)。

过滤后的地图甚至不使用std::map,因为没有必要这样做。 (条目已经排序,过滤由额外的std::set&lt;int&gt;完成。)

实际上,我不知道如何使用自定义谓词执行此操作,因为我不知道如何通过额外检查重复值来保持映射的(必需)顺序。


如果值已经存在于之前对应于不同键的映射中,是否有办法创建一个比较器以确保不插入另一个“键、值”?这将节省我通过创建另一个集合来使用的额外空间。

我已经考虑了一段时间。是的,这是可能的,但我不建议将它用于生产代码。

std::map::insert() 可能会调用std::map::lower_bound() 来查找插入点(即迭代器)。 (std::map::lower_bound() 反过来将使用我们的自定义谓词。)如果返回的迭代器是end(),则在末尾插入条目。否则,将此迭代器的键与作为新提供的(要插入的)键进行比较。如果相等,则拒绝插入,否则将在此处插入新条目。

因此,要拒绝插入具有重复值的条目,谓词必须返回false,而不考虑键的比较。为此,谓词必须进行额外检查。

要执行这些额外的检查,谓词需要访问整个映射以及要插入的条目的值。为了解决第一个问题,谓词获得了对其使用的地图的引用。对于第二个问题,我没有更好的主意来使用std::set&lt;std::pair&lt;std::string, int&gt; &gt; 而不是原来的std::map&lt;std::string, int&gt;。由于已经涉及到自定义谓词,因此可以充分调整排序行为。

所以,这就是我得到的:

#include <iostream>
#include <map>
#include <set>
#include <vector>

typedef std::pair<std::string, int> Entry;

struct CustomLess;

typedef std::set<Entry, CustomLess> Set;

struct CustomLess {
  Set &set;
  CustomLess(Set &set): set(set) { }
  bool operator()(const Entry &entry1, const Entry &entry2) const;
};

bool CustomLess::operator()(
  const Entry &entry1, const Entry &entry2) const
{
  /* check wether entry1.first already in set
   * (but don't use find() as this may cause recursion)
   */
  bool entry1InSet = false;
  for (const Entry &entry : set) {
    if ((entry1InSet = entry.first == entry1.first)) break;
  }
  /* If entry1 not in set check whether if could be added.
   * If not any call of this predicate should return false.
   */
  if (!entry1InSet) {
    for (const Entry &entry : set) {
      if (entry.second == entry1.second) return false;
    }
  }
  /* check wether entry2.first already in set
   * (but don't use find() as this may cause recursion)
   */
  bool entry2InSet = false;
  for (const Entry &entry : set) {
    if ((entry2InSet = entry.first == entry2.first)) break;
  }
  /* If entry2 not in set check whether if could be added.
   * If not any call of this predicate should return false.
   */
  if (!entry2InSet) {
    for (const Entry &entry : set) {
      if (entry.second == entry2.second) return false;
    }
  }
  /* fall back to regular behavior of a less predicate
   * for entry1.first and entry2.first
   */
  return entry1.first < entry2.first;
}

int main()
{
  // Creating & Initializing a map of String & Ints
  // with very specific behavior
  Set mapOfWordCount({
      { "aaa", 10 }, { "ddd", 41 }, { "bbb", 62 }, { "ccc", 10 }
    },
    CustomLess(mapOfWordCount));
  // output
  for (const Entry &entry : mapOfWordCount) {
    std::cout << "{ \"" << entry.first << "\", " << entry.second << " }\n";
  }
  // done
  return 0;
}

输出:

{ "aaa", 10 }
{ "bbb", 62 }
{ "ddd", 41 }

Live Demo on coliru

我的合作者将此称为弗兰肯斯坦解决方案,恕我直言,在这种情况下就足够了。

std::map/std::set 的意图通常是分摊的 insert() 和 find()。这种效果可能完全消失了,因为CustomLess 必须在整个集合上迭代(在最坏的情况下)两次,然后才能返回一个值。 (在某些情况下,可能从迭代中提前退出并没有太大帮助。)

所以,这是一个很好的谜题,我以某种方式解决了它,而是提出了一个反例。

【讨论】:

  • @CodeBeginner 我扩展了答案并考虑了您的最后一个要求,但我不建议这样做。赢得的内存是以相当糟糕的时间复杂度支付的。
【解决方案2】:

正如 cmets 中提到的@Galik,您的代码的问题在于 map 的比较函数需要两个键作为参数,而不是键值对。因此,您无法访问比较器中的值。

@Scheff 类似,我也看不到使用自定义比较器使您的解决方案以实用或推荐的方式工作的方法。但除了使用setvector,您还可以反转您的地图。然后可以通过map::insert()函数执行过滤:

#include <map>
#include <string>
#include <iostream>

int main() {
    // Creating & Initializing a map of String & Ints
    std::map<std::string, int> mapOfWordCount = { { "aaa", 10 }, { "ddd", 41 },
            { "bbb", 62 }, { "ccc", 10} };

    std::map<int, std::string> inverseMap;
    for(const auto &kv : mapOfWordCount)
        inverseMap.insert(make_pair(kv.second, kv.first));

    for(const auto& kv : inverseMap)
        std::cout << "{ \"" << kv.second << "\", " << kv.first << " }" << std::endl;
}

函数map::insert() 仅在它的键不存在于地图中时插入一个项目。输出:

{ "aaa", 10 }
{ "ddd", 41 }
{ "bbb", 62 }


但是,如果您要求目标映射 setOfWords 的类型为 std::map&lt;std::string, int&gt;,那么您可以通过以下方式再次从上面的代码中反转反转映射:

std::map<std::string, int> setOfWords;
for(const auto& kv : inverseMap)
    setOfWords[kv.second] = kv.first;

for(const auto& kv : setOfWords)
    std::cout << "{ \"" << kv.first << "\", " << kv.second << " }" << std::endl;

因此(即使这不是您的要求),setOfWords 将按键排序。输出:

{ "aaa", 10 }
{ "bbb", 62 }
{ "ddd", 41 }

Code on Ideone

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2010-11-07
    • 1970-01-01
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-12-23
    相关资源
    最近更新 更多