如何使用 C++11 中的函数式编程从映射中获取密钥

How to use functional programming in C++11 to obtain the keys from a map?

本文关键字:映射 获取 密钥 编程 函数 何使用 C++11      更新时间:2023-10-16

在数学观点中,std::map<K,V> m是一个函数f m,其中所有域和范围元素(x,y)对∈K × V,使得fm(x)= y。

所以,我想得到 fm 的域,即所有键的集合(或者可能是范围 - 所有值的集合)。我可以用 C++11 按程序执行此操作,如下所示:

std::unordered_set<K> keys;
for (const auto& kv_pair : m) { keys.insert(kv_pair->first); }

右?但是 - 我想在功能上做到这一点(阅读:以一种让我觉得优越的花哨方式)。我该怎么做?

笔记:

  • 我不一定需要结果是 std::unordered_set;可以替换这样一组的东西也可能起作用(例如,设置立面)。
  • 可读性、(合理)简洁和避免无端复制数据都是考虑因素。

Boost.Range恰恰提供了这一点,适配器map_keys .查看文档中的此示例。

你可以写:

auto keys = m | boost::adaptors::map_keys;
// keys is a range view to the keys in your map, no copy involved
// you can use keys.begin() and keys.end() to iterate over it

编辑:我将在下面留下我的旧答案,它使用迭代器而不是范围。请注意由两个boost::transform_iterator表示的范围仍表示地图中的键集。

IMO 执行此操作的功能方式需要一个指向映射键的迭代器,以便您可以简单地使用 std::copy .这是有道理的,因为你没有转换或积累任何东西,你只是在复制密钥。

不幸的是,该标准不提供迭代器适配器,但您可以使用 Boost.Iterator 提供的适配器。

#include <algorithm>
#include <map>
#include <unordered_set>    
#include <boost/iterator/transform_iterator.hpp>
struct get_first
{
    template<class A, class B>
    const A & operator()(const std::pair<A,B> & val) const
    {
        return val.first;
    }
};
int main()
{
    std::map<int, std::string> m;
    std::unordered_set<int> r;
    // ...
    std::copy(boost::make_transform_iterator(m.begin(), get_first{}),
              boost::make_transform_iterator(m.end(), get_first{}),
              std::inserter(r, r.end()) );
}

让一个迭代器取消引用元组/对的 Kth 元素会更有表现力,但transform_iterator可以很好地完成这项工作。

我直言,直观函数式代码的一个重要特征是算法实际上返回结果,而不是在其他地方设置一些变量作为副作用。 这可以通过std::accumulate来完成,例如:

#include <iostream>
#include <set>
#include <map>
#include <algorithm>
int main()
{
    typedef std::map<int, int> M;
    M m { {1, -1}, {2, -2}, {3, -3}, {4, -4} };
    auto&& x = std::accumulate(std::begin(m), std::end(m), std::set<int>{},
                               [](std::set<int>& s, const M::value_type& e)
                               {
                                  return s.insert(e.first), std::move(s);
                                                // .first is key,
                               });              // .second is value
    for (auto& i : x)
        std::cout << i << ' ';
    std::cout << 'n';
}

输出:

1 2 3 4 

看它在这里运行

std::begin(m), std::end(m)位实际上是一个令人头疼的问题,因为它阻碍了此类操作的链接。 例如,如果我们可以将"功能"操作(如上面的"GET KEYS")与其他操作链接在一起,那将是理想的......

x = m. GET KEYS . SQUARE THEM ALL . REMOVE THE ODD ONES

。或者至少...

x = f(f(f(m, GET KEYS), SQUARE THEM ALL), REMOVE THE ODD ONES)

。但是你必须自己编写一些琐碎的代码才能到达那里,或者选择一个支持函数式"风格"的库。

有很多方法可以写这个。一种稍微"实用"的方式是:

vector<string> keys;
transform(begin(m), end(m), back_inserter(keys), [](const auto& p){ return p.first; });

但是为了真正改进这一点并使用标准库实现更实用的风格,我们需要像 Eric Niebler 的 Range Proposal 这样的东西来标准化。与此同时,有许多基于范围的非标准库,如 Eric 的 range-v3 和 boost Range,您可以使用它们来获得更实用的样式。

std::map<int, int> m;
std::unordered_set<int> keys;
std::for_each(m.begin(), m.end(), [&keys](decltype(*m.begin()) kv)-> void {keys.insert(kv.first);});