【问题标题】:Is it possible to initialize a vector from the keys in a map?是否可以从地图中的键初始化向量?
【发布时间】:2014-10-13 16:34:46
【问题描述】:

How to retrieve all keys (or values) from a std::map and put them into a vector? 涵盖了从 C++11 之前的映射中的键填充 std::vector 的方法。

有没有办法在 C++11 中使用 lambdas 等来做到这一点,这意味着我们可以在一行中完成,这样我们就可以从映射中初始化向量,而不是创建一个向量并在 2 个动作中填充它?

例如vector<int> v(???(m.begin(),m.end()));

纯 C++11 是首选,但 boost 是可以接受的...目的是在一行中完成此操作而不会过于复杂和“炫耀”,因此不会让其他开发人员感到困惑。

为了比较,“明显”的 C++11 解决方案是:

vector<int> v;
v.reserve(m.size()); //not always needed
for(auto &x : map)
  v.push_back(x.first)

【问题讨论】:

  • An answer 在那个帖子中已经有了 C++11 解决方案。
  • 一个解决方案,但不一定是最好的。

标签: c++ c++11


【解决方案1】:

你去吧,C++11 单行:)

#include <iostream>
#include <map>
#include <vector>
#include <algorithm>
#include <iterator>

int main(int, char**)
{
    std::map<int, std::string> map {
        {1, "one"},
        {2, "two"},
        {3, "three"}
    };
    std::vector<int> keys;


    // Reserve enough space (constant-time)
    keys.reserve(map.size());

    // Retrieve the keys and store them into the vector
    std::transform(map.begin(), map.end(), std::back_inserter(keys),
        [](decltype(map)::value_type const &pair) {return pair.first;}
    );//   ^^^^^^^^^^^^^^^^^^^^^^^^^ Will benefit from C++14's auto lambdas


    // Display the vector
    std::copy(keys.begin(), keys.end(),
        std::ostream_iterator<int>(std::cout, " "));

    return 0;
}

std::transform 太强大了。

【讨论】:

  • 正如我所提到的,back_inserter 将为您调整向量的大小,从而导致多次内存分配和数据复制。它还将增加每个元素的元素计数器。使用转换迭代器时,您没有这些对性能有害的操作。
  • @Oktalist 将转发迭代器传递给vector 的构造函数时,它会分配一次存储空间,将数据成员设置为其最终值,然后调用uninitialized_copy。
  • 您会看到back_inserter 的简单单行代码充满了性能问题。 vector::reserve 使性能更好,但是现在它会使您的代码变得混乱。转换迭代器不会出现此类问题。我不确定 transform 是否在这里得到保证,您可以直接执行 range-for-loop:for(auto&amp; kv : m) keys.push_back(kv.first) - 更易于输入,更易于阅读。
  • 我很想看看你的单线。只需用逗号替换分号,我的就是,如果这对你很重要的话。也许改天我会为 Boost 迭代器献上一座雕像。
  • 这似乎失败了,因为它需要多行,而且过于复杂。一个简单的for(auto x : map){...} 可能代码更少而且更简单。
【解决方案2】:

不,在纯 C++11 中,没有办法在 一行 中使用任何 std::vectors 构造函数重载来做到这一点,而无需例如创建您自己的迭代器适配器。

虽然在 两 行中做这件事是微不足道的,例如:

std::vector<Key> v;
for (const auto& p : m) v.push_back(p.first);

为此创建自己的迭代器适配器也很容易,例如:

template <typename InputIt>
struct key_it : public InputIt {
    key_it(InputIt it) : InputIt(it) {}
    auto operator*() { return (*this)->first; }
};

// Helper function for automatic deduction of iterator type.
template <typename InputIt>
key_it<InputIt> key_adapt(InputIt it) {
    return {it};
}

现在您可以使用以下命令在一行中创建和填充您的std::vector:

std::vector<Key> v{key_adapt(std::begin(m)), key_adapt(std::end(m))};

Live example

【讨论】:

  • 我认为一个简单的循环可能和它一样好。必须编写自己的仿函数/类正是我希望 C++11 允许我避免的:(
  • @John 我认为这是最好的,除非你想陷入 Boost 的泥潭......
【解决方案3】:

Quentin's solution的轻微改进:

std::vector<int> keys(map.size());
transform(map.begin(), map.end(), keys.begin(),
    [](std::pair<int, std::string> const &p) { return p.first; });

或更易读:

std::vector<int> keys(map.size());
auto get_key = [](std::pair<int, std::string> const &p) { return p.first; };
transform(map.begin(), map.end(), keys.begin(), get_key);

可能更好:

int get_key(std::pair<int, std::string> const &p) { return p.first; }

std::vector<int> get_keys(const std::map<int, std::string> &map)
{
    std::vector<int> keys(map.size());
    transform(map.begin(), map.end(), keys.begin(), get_key);
    return keys;
}

然后调用:

std::vector<int> keys = get_keys(map);

如果它会被大量使用。

【讨论】:

  • 它对vector的元素进行了不必要的默认初始化。
  • 我缺乏 C++ 经验表明 :) 这可能不是问题,具体取决于您使用它的位置。
【解决方案4】:

哎呀,我知道这实际上并不能回答你的问题(必须学会正确阅读)!

我可能会选择这样的东西:

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

int main()
{
    std::map<int, std::string> m =
    {
        {1, "A"}, {2, "B"}, {3, "C"}
    };

    std::vector<int> v;
    v.reserve(m.size());

    for(auto&& i: m)
        v.emplace_back(i.first);

    for(auto&& i: v)
        std::cout << i << '\n';
}

【讨论】:

    【解决方案5】:

    在 Boost.Range 中使用 boost::adaptor::map_keys。

    #include <iostream>
    #include <vector>
    #include <map>
    #include <boost/range/adaptor/map.hpp>
    
    int main()
    {
        const std::map<int, std::string> m = {
            {1, "Alice"},
            {2, "Bob"},
            {3, "Carol"}
        };
    
        auto key_range = m | boost::adaptors::map_keys;
        const std::vector<int> v(key_range.begin(), key_range.end());
    
        for (int x : v) {
            std::cout << x << std::endl;
        }
    }
    

    输出:

    1
    2
    3
    

    【讨论】:

    • 您可以在这里使用boost::copy_range 函数来节省一些输入。
    • 我不喜欢这个功能。更好的方法是在我的库中使用as_container。 const std::vector&lt;int&gt; v = m | map_keys | as_container; github.com/faithandbrave/OvenToBoost 但是,这还没有包含在 Boost.Range 中。 (现在提议)
    • 是的,你的as_container 比copy_range 好。
    • 这看起来很酷,但我不确定它是否满足普通 C++11 用户的“不混淆”要求:p
    • map_keys 行为已记录在案。任何方法都需要了解。
    【解决方案6】:

    如下所示:

    #include <vector>
    #include <map>
    #include <boost/iterator/transform_iterator.hpp>
    
    int main() {
        std::map<std::string, int> m{{"abc", 1}, {"def", 2}};
        auto extractor = [](decltype(m)::value_type const& kv) { return kv.first; };
        std::vector<std::string> v(
              boost::make_transform_iterator(m.begin(), extractor)
            , boost::make_transform_iterator(m.end(), extractor)
            );
    }
    

    请注意,与使用push_back 或resize 的解决方案相比,将迭代器传递给vector 的构造函数是初始化vector 的最有效方法,首先使用默认值填充它。

    【讨论】:

      【解决方案7】:

      std::vector 类有两个相关的构造函数:

      1. vector(std::initializer_list&lt;T&gt;)[C++11]
      2. vector(InputIterator first, InputIterator last) [C++98]

      第一个是新的 C++11 构造函数,它允许您执行以下操作:

      std::vector<int> v{ 1, 2, 3 };
      

      第二个允许您执行以下操作:

      std::vector<int> w{ v.rbegin(), v.rend() }; // 3, 2, 1
      

      我看不到使用 initializer_list 构造函数的方法(因为您没有预先可用的项目),所以最好的办法是创建一个适用于 @ 的 key_iterator 类987654328@ 并返回 (*i).first 而不是 (*i)。例如:

      std::vector<int> keys{ key_iterator(m.begin()), key_iterator(m.end()) };
      

      这还需要您编写key_iterator 类,您可以使用Boost 迭代器适配器来简化任务。使用 2 行版本可能更容易。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-02-20
        • 2014-07-26
        • 1970-01-01
        • 1970-01-01
        • 2015-09-29
        • 1970-01-01
        相关资源
        最近更新 更多