【问题标题】:idiomatic C++ for creating a std::vector from the last n elements of a std::map用于从 std::map 的最后 n 个元素创建 std::vector 的惯用 C++
【发布时间】:2012-03-12 14:43:44
【问题描述】:

从 std::map 的最后 n 个元素创建 std::vector 的 C++ 惯用方式是什么?

我对保留向量中的顺序不感兴趣。

我可以复制元素,像这样:

    std::map< double, MyType > m;
    size_t n = 3;
    std::vector< MyType > v;
    std::map< double, MyType >::iterator it = m.end();
    while ( n-- ) { // assuming m.size() >= n
        it--;
        v.push_back(it->second);
    }

但是,还有其他更习惯的方法吗?

【问题讨论】:

  • double 是地图的 非常 错误键类型。浮点值的精度使其通常无法使用。
  • @Xeo 感谢您的评论。 “无法使用”是什么意思?
  • 好吧,假设你是map[13.37] = blargh;。例如,不能保证使用map[13.07 + 0.3]double d = 13.37; map[d] ... 得到相同的结果,因为并非每个数字都可以精确地表示为浮点值。谷歌应该提供更多信息,“浮点数不准确”。
  • @Xeo 好的,没关系。幸运的是,我没有想到以您显示的有问题的方式使用该地图:-)

标签: c++ vector map idioms


【解决方案1】:

std::copy 如果您想复制类型不变,这将是合适的。但是,std::map&lt;T,U&gt;::iterator_type::value_type 不是U(您要复制的类型),而是std::pair&lt;T,U&gt;(换句话说,取消引用映射迭代器会产生一对键和值类型),因此原始副本不会工作。

所以我们需要复制元素,并在此过程中执行转换。这就是std::transform 的用途。

为方便起见,我假设您的编译器支持 C++11 lambda 表达式和 auto 关键字。如果没有,它可以相当简单地重写为函子。但我们正在寻找大致这样的东西:

std::transform(map_first, map_last, std::back_inserter(vec), [](std::pair<double,MyType> p) { return p.second; });

现在我们只需要填写前两个参数:

auto map_first = std::next(map.end(), -n); 
auto map_last = map.end();

这里唯一棘手的部分是映射迭代器是双向的,但不是随机访问的,所以我们不能简单地说map.end() - n- 运算符未定义。相反,我们必须使用std::next(双向运算符需要线性而不是恒定的时间,但没有办法解决这个问题)。

(请注意,我没有尝试编译此代码,因此可能需要稍作调整)

【讨论】:

  • 这是一个好的开始。 (我特别喜欢使用 lambda。)但是如果地图不包含 n 元素会怎样?你也需要检查一下。
  • map::value_typestd::pair&lt;const T, U&gt;,顺便说一句。
  • @JamesKanze:是的。我试图省略所有不必要的东西,例如错误检查,因为这只会混淆示例代码,我更喜欢让它尽可能简短和简单。但显然,要真正使用代码,您必须处理可能的错误情况。
  • @jalf 嗯。不确定我是否同意“非必要的东西,例如错误检查”的说法。更重要的是,我不确定省略它是一个好主意,因为很多程序员在实际代码中确实忘记了它。最好树立一个好榜样(或至少提到它已被省略)。
  • 在回答所提出的问题时无关紧要。是的,它在实际编写工作代码时是必不可少的,但我方便地省略了许多其他事情。 OP询问如何从地图的最后N个元素创建一个向量(这意味着地图中的最后N个元素实际上存在),所以这是我试图回答的问题。无论如何,我只是按照个人喜欢的方式去做,当我问问题时,我更喜欢直截了当的答案,不要试图将它与六种事物捆绑在一起我不需要知道。 :) YMMV
【解决方案2】:

std::transform 将是最惯用的方式。你需要一个函数式 对象:

template<typename PairType>
struct Second
{
    typename PairType::second_type operator()( PairType const& obj ) const
    {
        return obj.second;
    }
}

(如果您使用 std::map 或其他使用 std::pair,你的工具箱里就有这个。)

之后,这有点尴尬,因为你只想要最后一个 n。自从 地图中的迭代器不是随机访问迭代器,您不能添加 或减去任意值,最简单的解决方案是将它们全部复制, 然后删除你不想要的:

std::vector<MyType>
extractLastN( std::map<double, MyType> const& source, size_t n )
{
    std::vector<MyType> results;
    std::transform( source.begin(), source.end(),
                    std::back_inserter( results ),
                    Second<std::map<double, MyType>::value_type>() );
    if ( results.size() > n ) {
        results.erase( results.begin(), results.end() - n );
    }
    return results;
}

这不是最有效的,但取决于n 及其所在位置 用过,可能就够了。如果您确实想避免额外的复制, 等等(只有当n 通常比 地图的大小),你必须做一些更有趣的事情:

std::vector<MyType>
extractLastN( std::map<double, MyType> const& source, ptrdiff_t n )
{
    std::map<double, MyType>::const_iterator start
            = source.size() <= n
                ? source.begin()
                : std::prev( source.end(), n );
    std::vector<MyType> results;
    std::transform( start, source.end(),
                    std::back_inserter( results ),
                    Second<std::map<double, MyType>::value_type>() );
    return results;
}

(如果您无法访问 C++11,std::prev 很简单:

template<typename IteratorType>
IteratorType
prev( IteratorType start, ptrdiff_t n )
{
    std::advance( start, -n );
    return start;
}

同样,如果您对标准库做了很多工作,您可能 已经在你的工具包中了。)

【讨论】:

  • 我总是忘记std::transform
  • @James Kanze 谢谢,感谢您的错误检查。我知道这是非常主观的(没有任何规定的要求),但是如果地图的大小为 ,你为什么不在你的 extractLastN 中抛出一个异常?
  • @uvts_cvs 因为它不符合我通常需要的东西:在大多数我需要这样的东西的情况下,n 被理解为最大值,但更少是可以接受的。如果不是这种情况,那么当然需要进行一些其他处理(可能是assert,也可能是一个例外)。
【解决方案3】:

这是一个简单的 Boost.Range 版本:

#include <boost/range/iterator_range_core.hpp>
#include <boost/range/adaptor/map.hpp>
//#include <boost/range/adaptor/reversed.hpp> // comment in for 'reversed'
#include <map>
#include <vector>

struct X{};

int main(){
  std::map<int, X> m;
  unsigned n = 0;
  auto vec(boost::copy_range<std::vector<X>>(
    boost::make_iterator_range(m, m.size()-n, 0)
    | boost::adaptors::map_values
    //| boost::adaptors::reversed // comment in to copy in reverse order
  ));
}

【讨论】:

    【解决方案4】:

    一种方法是使用简单的for_each

        map<int,double> m;
        vector<double> v;
        //Fill map
        auto siter = m.end();
        advance(siter, -3);
        for_each(siter, m.end(), [&](pair<int,double> p) { v.push_back(p.second); });
    

    编辑更简单的方法是使用std::prevfor_each

        map<int,double> m;
        vector<double> v;
        //Fill map
        for_each(prev(m.end(), 3), m.end(), 
                      [&](pair<int,double> p) { v.push_back(p.second); });
    

    另外,如果你想以相反的顺序填充向量,你可以使用:

    for_each(m.rbegin(), next(m.rbegin(), 3), 
            [&](pair<int,double> p) { v.push_back(p.second); });
    

    【讨论】:

    • 但是std::copystd::transform 的存在是有原因的...... :)
    • @jalf:不过,我很确定原因不是强迫你写几十行代码来将所有可能的东西变成transform
    • 你从哪里得到“几十行代码”?为什么使用transform 的答案会更长?
    【解决方案5】:

    首先,我们想写什么是惯用的?我建议:

    std::vector<Mytype> v;
    v.reserve(n);
    std::transform(
      limited(n, m.rbegin()), 
      limited_end(m.rend()),
      std::back_inserter(v),
      values_from(m)
    );
    

    现在我们只需使该代码有效。

    我们可以用 lambda 替换 values_from(m)(不需要 m),或者使用 James Kanze 的类 Second 来实现它(在这种情况下,m 用于类型推导):

    template <typename Map>
    Second<Map::value_type> values_from(const Map &) {
        return Second<Map::value_type>();
    }
    

    实现limited 有点痛苦。它是一个返回迭代器的模板函数。它返回的迭代器类型是一个模板,它包装了另一个迭代器类型,并将所有内容转发给它,除了它跟踪n,并且当它被推进n 次时就像一个结束迭代器。 limited_end 返回相同类型的结束迭代器,因此如果底层迭代器相等,它比较相等 either,如果迭代器之一是使用 limited_end 创建的,则比较相等 而另一个已将n 运行到零。

    我没有方便的代码,但它基本上是您获得所有标准算法的_n 等效项的方式,而不仅仅是copy_n。在这种情况下,我们需要transform_n,但没有。

    transform 的替代方法是使用 copy_nboost::transform_iterator 包裹 m.rbegin()。我也不会在这里这样做,因为我总是需要查看文档才能使用transform_iterator

    【讨论】:

    • 请注意,这会以相反的顺序填充向量。 :)
    • @Xeo:它以与提问者代码相同的顺序填充向量。其他人都以相反的顺序填写它;-) 值得注意的是未来使用的差异,但提问者并不关心顺序。
    • 哈,你是对的..该死的。 :) FWIW,而不是 transform_iterator,只需使用来自 Boost.Range 的 transformed 适配器。或者,因为它会归结为同样的事情,就像我的回答一样,只需map_values。 :)
    【解决方案6】:

    倒着做:

    assert(n <= m.size());
    std::copy(m.rbegin(), m.rbegin()+n, std::back_inserter(v));
    

    双向迭代器 FTW。


    好吧,我显然还没醒,那是一厢情愿。不过,我仍然更喜欢向后走以获得最后一个 n,因此工作版本如下所示:

    #include <map>
    #include <vector>
    #include <algorithm>
    #include <iterator>
    
    using namespace std;
    
    // I'm assuming it's ok to copy min(m.size(), n)
    template <typename Iter>
    Iter safe_advance(Iter begin, Iter const &end, int distance)
    {
        while(distance-- > 0 && begin != end)
            ++begin;
        return begin;
    }
    
    void copy_last_n(map<double,int> const &m,
                     vector<int> &v, int n)
    {
        transform(m.rbegin(), safe_advance(m.rbegin(), m.rend(), n),
                  back_inserter(v),
                  [](map<double,int>::value_type const &val)
                  {
                    return val.second;
                  }
                 );
    }
    

    James Kanze 已经编写了 Second 函子 - 如果您无法访问 lambda,请使用它。

    【讨论】:

    • 但在向量中,OP 只想要地图的值部分。
    • 好点!我现在可能不得不把它作为练习留给读者,稍后再回来......
    • 地图迭代器上也没有 + 运算符。换句话说,这根本行不通。 :)
    • 如果地图包含的元素少于n,则会出现未定义的行为。
    • 我断言了大小,但是是的,结果完全失败了。稍后会修复,除非到那时已经接受了更好的方法。
    猜你喜欢
    • 2011-12-27
    • 1970-01-01
    • 2011-11-13
    • 2021-08-13
    • 1970-01-01
    • 1970-01-01
    • 2016-08-29
    • 1970-01-01
    • 2016-03-30
    相关资源
    最近更新 更多