如何从另一张地图构造一张地图?

时间:2018-07-29 12:01:33

标签: c++ dictionary stl

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

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

#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(_ArgTypes ...)>              ^ /opt/tools/installs/gcc-4.8.3/include/c++/4.8.3/functional:2466:5:   注意:_Res std :: function <_Res(_ArgTypes ...)> :: operator()(_ ArgTypes   ...)const [with _Res = bool; _ArgTypes =   {std :: pair,   std :: allocator>,int>,std :: pair,std :: allocator>,int>}]        函数<_Res(_ArgTypes ...)> ::        ^

2 个答案:

答案 0 :(得分:2)

这是根据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<Key>)的自定义谓词就足以解决问题(对于mapset)。

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

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


  

是否存在一种创建比较器的方法,以确保未插入另一个“键,值”(如果该值先前已存在于映射中,且先前对应于另一个键)?这样可以节省额外的空间,我可以通过创建另一个集合来使用该空间。

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

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

因此,要拒绝插入具有重复值的条目,无论键的比较如何,谓词都必须返回false。为此,谓词必须进行额外的检查。

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

所以,这就是我得到的:

#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

我的合作伙伴将其称为 Frankenstein解决方案,恕我直言,在这种情况下,这就足够了。

std::map / std::set的意图通常是摊销insert()和find()。由于CustomLess必须在整个集合上进行两次迭代(在最坏的情况下)才能返回值,因此这种效果可能会完全消失。 (在某些情况下,迭代可能会提早解决,但效果不大。)

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

答案 1 :(得分:1)

如注释中的@Galik所示,您的代码存在的问题是,映射的比较函数需要两个键作为参数,而不是键值对。因此,您无权访问比较器中的值。

类似于@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<std::string, int>,则可以通过以下方式再次从上面的代码中反转倒置的地图:

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