从两个向量创建地图

13

如果我有两个stl向量vect1、vect2,我想从它们中产生一个映射,使得第一个元素在vect1中对应于第一个元素在vect2中,依此类推。 最简单的方法是什么?


1
两个大小不同的物体发生碰撞时,您希望出现什么情况? - GManNickG
5个回答

10

这里有一个解决方案,它使用了标准库函数(以及C++0x lambda表达式)。

const int data1[] = { 0, 2, 4, 6, 8 };
const int data2[] = { 1, 3, 5, 7, 9 };
std::vector<int> vec1(data1, data1 + 5);
std::vector<int> vec2(data2, data2 + 5);
std::map<int,int> map;

// create map
std::transform(vec1.begin(), vec1.end(), vec2.begin(), std::inserter(map, map.end()), [](int a, int b)
{
    return std::make_pair(a, b);
});

// display map
std::for_each(map.begin(), map.end(), [](const std::pair<int,int>& p)
{
    std::cout << p.first << "," << p.second << "\n";
});

注意:这假设 vec1.size() 不大于 vec2.size()。


3
在C++03中,你不需要使用lambda表达式,以下代码同样有效:std::transform(vec1.begin(), vec1.end(), vec2.begin(), std::inserter(my_map, my_map.end()), std::make_pair<int, std::string>); - Nim
@Nim:也许只有我一个人这样,但 MSVC++ 不接受 std::make_pair 作为二元函数对象。 - Blastfurnace
你尝试过使用std::make_pair<int, int>吗?没有模板参数的make_pair将无法解析。 - CashCow
@CashCow:是的,但它仍然无法编译。我还尝试使用std::bind将其包装起来,结果相同。Lambda再次获胜。 - Blastfurnace

9
std::vector<int> a, b;
// fill vectors here...
std::map<int, int> m;
assert(a.size() == b.size());
for (size_t i = 0; i < a.size(); ++i)
    m[a[i]] = b[i];

2
这个可以运行,但我会用异常或其他方式替换assert。没有人喜欢一个会停止程序的库调用! - André Caron
a 包含重复项时会发生什么?您可能想要执行 if (m.find(a[i]) != m.end()) m.insert(a[i]) else throw ... - Alexandre C.
1
@Alexandre C. insert 将返回一个带有元素迭代器和布尔值的对,指示它是否被插入或之前已存在。不需要进行先前的查找。 - David Rodríguez - dribeas
@大卫:我有时候应该读一下文档!谢谢你。所以我的注释应该是这样阅读的 if (!m.insert(std::make_pair(a[i], b[i])).second) throw ... - Alexandre C.
如果存在重复的键,此版本将覆盖它,因此它将具有最后一个遇到的值。 - CashCow
显示剩余2条评论

8
我们将使用接受两个输入序列的 std::transform 版本。(似乎不像接受单个序列的版本那么为人所知。)
您可以将 std::make_pair<v1::value_type, v2::value_type> 作为您的转换器(op)传入,因此在您的情况下:
std::vector<int> vec1, vec2;
std::map< int, int > mergedMap;
std::transform( vec1.begin(), vec1.end(), vec2.begin(), 
       std::inserter(mergedMap, mergedMap.end() ), std::make_pair<int const&,int const&> );

我已经测试了这段代码,并且在GNU 4.3.2下编译良好。

(我现在也测试了C ++ 11。当我将make_pair更改为int const&而不是int时,它可以工作)。

如果两个输入序列的长度不同,则第一个较短,第二个序列中的后续元素将被忽略,这样就没问题了。如果第一个序列较长,则会产生未定义的行为。


1
标准库已经有一个std::transform算法,它接受两个序列和一个二元函数对象。 - Blastfurnace
@Blastfurnace,嗯,我上次查看时不是这样的。std::transform接受一个序列和一个输出迭代器(用于写入转换后的结果)... - Nim
@Nim:我发布了一个使用std::transform的答案。我还作弊地使用了C++0x lambda表达式,因为它使得整个过程变得如此简单。 - Blastfurnace
哈哈,也许只有我认为 Lambda 版本看起来很糟糕... ;) - Nim
这个程序无法使用 -std=c++11 选项编译。我在 g++、clang++ 和 icpc 中都遇到了同样的错误:/usr/include/c++/4.8.1/bits/stl_algo.h:4988:46: error: cannot bind ‘int’ lvalue to ‘int&&’。 Lambda 表达式在 c++11 中表现良好,可能是解决问题的最佳方式。 - DarioP
显示剩余9条评论

1

这里有一个稍微变化的版本,它使用了boost的zip_iterator

#include <iostream>
#include <algorithm>
#include <string>
#include <map>
#include <vector>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/tuple/tuple.hpp>

// this is our map type
typedef std::map<int, std::string> map_t;

// this functor will be called for each "pair"
struct map_adder :
  public std::unary_function<const boost::tuple<const int&, const std::string&>&, void>
{
  map_adder(map_t& my_map) : _my_map(my_map){}

  void operator()(const boost::tuple<const int&, const std::string&>& t) const
  {
    _my_map.insert(std::make_pair(t.get<0>(), t.get<1>()));
  }

private:
  mutable map_t& _my_map;
};

int main(void)
{
  // test setup
  std::vector<int> keys;
  std::vector<std::string> values;
  keys.push_back(1);
  keys.push_back(2);
  keys.push_back(3);
  keys.push_back(4);

  values.push_back("1");
  values.push_back("2");
  values.push_back("3");
  values.push_back("4");

  std::vector<int>::const_iterator beg1 = keys.begin();
  std::vector<int>::const_iterator end1 = keys.end();
  std::vector<std::string>::const_iterator beg2 = values.begin();
  std::vector<std::string>::const_iterator end2 = values.end();

  // destination
  map_t my_map;

  // functor to actually add
  map_adder adder(my_map);

  // simply iterate over...
  std::for_each(
    boost::make_zip_iterator(
      boost::make_tuple(beg1, beg2)
      ),
    boost::make_zip_iterator(
      boost::make_tuple(end1, end2)
      ),
    adder
  );

  std::cout << "size of map: " << my_map.size() << std::endl;

  return 0;
}

好的,这里有一个使用std::transform更简单的版本,我不知道是否已经存在可以将boost::tuple转换为std::pair的东西,因此我写了这个简单的函数...

#include <iostream>
#include <algorithm>
#include <string>
#include <iterator>
#include <map>
#include <vector>
#include <boost/iterator/zip_iterator.hpp>
#include <boost/tuple/tuple.hpp>

// this is our map type
typedef std::map<int, std::string> map_t;

map_t::value_type adapt_tuple(const boost::tuple<const map_t::key_type&, const map_t::mapped_type&>& t)
{
  return map_t::value_type(t.get<0>(), t.get<1>());
}

int main(void)
{
  std::vector<int> keys;
  std::vector<std::string> values;
  keys.push_back(1);
  keys.push_back(2);
  keys.push_back(3);
  keys.push_back(4);

  values.push_back("1");
  values.push_back("2");
  values.push_back("3");
  values.push_back("4");

  std::vector<int>::const_iterator beg1 = keys.begin();
  std::vector<int>::const_iterator end1 = keys.end();
  std::vector<std::string>::const_iterator beg2 = values.begin();
  std::vector<std::string>::const_iterator end2 = values.end();

  map_t my_map;

  // simply iterate over...
  std::transform(
    boost::make_zip_iterator(
      boost::make_tuple(beg1, beg2)
      ),
    boost::make_zip_iterator(
      boost::make_tuple(end1, end2)
      ),
    std::inserter(my_map, my_map.end()),
    adapt_tuple
    );

  std::cout << "size of map: " << my_map.size() << std::endl;

  return 0;
}

我们能否用 transform 替换 for_each,这将给你一个类似于我的解决方案? - CashCow
@CashCow,这是std::transform版本,唯一我不知道的是是否已经有可以将boost::tuple自适应为std::pair的东西,但是如果都遵循规范那还有什么乐趣呢?;) - Nim

0
假设您要忽略额外的(vect1的大小不等于vect2的大小),这可能是一个解决方案:
map<T1, T2> target; //vector<T1> vect1, vector<T2> vect2;
vector<T1>::iterator it1 = vect1.begin();
vector<T2>::iterator it2 = vect2.begin();
while(it1 != vect1.end() && it2 != vect2.end())
{
target.insert(std::make_pair(*it1, *it2));
it1++;it2++;
}

编辑:感谢 Nim 指出 *it1 的问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接