繁体   English   中英

如何转换 std::vector<T> 到对 std::vector 的向量<std::pair<T,T> &gt; 使用 STL 算法?

[英]How can I convert std::vector<T> to a vector of pairs std::vector<std::pair<T,T>> using an STL algorithm?

我有一个整数向量:

std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};

鉴于values.size()将始终是偶数。

我只是想将相邻元素转换成一对,如下所示:

std::vector<std::pair<int,int>> values = { {1,2}, {3,4} , {5,6}, {7,8} ,{9,10} };

即,两个相邻的元件连接成一对。

我可以使用什么 STL 算法轻松实现这一目标? 是否可以通过一些标准算法来实现这一点?

当然,我可以很容易地编写一个老式的索引for循环来实现这一点。 但我想知道使用基于范围的 for循环或任何其他 STL 算法(如std::transform等)最简单的解决方案是什么样的。

一旦我们有了 C++23 对<ranges>的扩展,您就可以使用std::ranges::views::chunk获得大部分的方法,尽管这会产生子范围,而不是对。

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto chunk_to_pair = [](auto chunk)
    {
        return std::pair(*chunk.begin(), *std::next(chunk.begin()));
    };
    for (auto [first, second] : values | std::ranges::views::chunk(2) | std::ranges::views::transform(chunk_to_pair))
    {
        std::cout << first << second << std::endl;
    }
}

或者,您可以通过zip一对stride d 视图来获得类似的结果

#include <iostream>
#include <ranges>
#include <vector>

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto odds = values | std::ranges::views::drop(0) | std::ranges::views::stride(2);
    auto evens = values | std::ranges::views::drop(1) | std::ranges::views::stride(2);
    for (auto [first, second] : std::ranges::views::zip(odds, evens))
    {
        std::cout << first << second << std::endl;
    }
}

最后一个可以推广到 n 元组

template <size_t N>
struct tuple_chunk_t
{
    template <typename R, size_t... Is>
    auto impl(R && r, std::index_sequence<Is...>)
    {
        using namespace ranges::view;
        return zip(r | drop(Is) | stride(N)...);
    }
    
    template <typename R>
    auto operator()(R && r) const
    {
        return impl(std::forward<R>(r), std::make_index_sequence<N>{});
    }
    
    template <typename R>
    friend auto operator|(R && r, chunk_t)
    {
        return impl(std::forward<R>(r), std::make_index_sequence<N>{});
    }
};

template <size_t N>
constexpr tuple_chunk_t<N> tuple_chunk;

我不确定为什么在自己编写时需要标准算法大约是 5 行代码(加上样板文件):

template<class T>
std::vector<std::pair<T, T>> group_pairs(const std::vector<T>& values)
{
    assert(values.size() % 2 == 0);
    auto output = std::vector<std::pair<T, T>>();
    output.reserve(values.size()/2);
    for(size_t i = 0; i < values.size(); i+=2)
        output.emplace_back(values[i], values[i+1]);
    return output;
}

并这样称呼它:

std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
auto result = group_pairs(values)

现场演示

我不知道可以直接执行您想要的标准算法(尽管我对 C++20 及更高版本不太熟悉)。 您始终可以编写一个循环,并且大多数循环可以通过标准算法std::for_each表示。


当您成对累积元素时,我会尝试std::accumulate

#include <vector>
#include <numeric>
#include <iostream>

struct pair_accumulator {
    std::vector<std::pair<int,int>> result;
    int temp = 0;
    bool set = false;
    pair_accumulator& operator+(int x){
        if (set) {
            result.push_back({temp,x});
            set = false;
        } else {
            temp = x;
            set = true;
        }
        return *this;
    }
};

int main() {
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    auto x = std::accumulate(values.begin(),values.end(),pair_accumulator{}).result;
    for (const auto& e : x) {
        std::cout << e.first << " " << e.second << "\n";
    }
}

诚然,这是否比编写普通循环更简单是值得怀疑的。


如果可能的话,我会尽量不转换向量。 除了访问result[i].first您还可以使用values[i*2]和类似的second 如果这不可行,则下一个选项是从一开始就填充std::vector<std::pair<int,int>>这样您就不必进行转换。 首先,根据您的详细需求,以下可能是一个开始:

#include <vector>
#include <iostream>

struct view_as_pairs {
    std::vector<int>& values;

    struct proxy {
        std::vector<int>::iterator it;
        int& first() { return *it;}
        int& second() { return *(it +1); }
    };
    proxy operator[](size_t index){
        return proxy{values.begin() + index*2};
    }
    size_t size() { return values.size() / 2;}

};

int main() {
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    view_as_pairs v{values};
    for (size_t i=0; i < v.size(); ++i){
        std::cout << v[i].first() << " " << v[i].second() << "\n";
    }
}

TL;DR:考虑一下你是否可以避免这种转变。 如果你不能避免它,写一个循环可能是最干净的。 标准算法经常提供帮助,但并非总是如此。

好的,我在评论中暗示了使用std::adjacent_find ,所以这就是你将如何做到这一点。

是的,很多人(甚至我自己)都认为这是一种 hack,我们正在使用一种用于其他目的的工具来解决看似无关的问题:

#include <algorithm>
#include <iostream>
#include <utility>
#include <vector>

int main()
{
   //Test data  
   std::vector<int> v = {1,2,3,4,5,6,7,8,9,10};

   // results 
   std::vector<std::pair<int,int>> result;

   // save flag 
   bool save_it = true;

   // Use std::adjacent_find 
   std::adjacent_find(v.begin(), v.end(), [&](int n1, int n2) 
      { if (save_it) result.push_back({n1,n2}); save_it = !save_it; return false; });
          
   for (auto& pr : result)
       std::cout << pr.first << " " << pr.second << "\n";
}

输出:

1 2
3 4
5 6
7 8
9 10

它的工作方式是我们忽略第二、第四、第六等对,只保存第一、第三、第五等对。 这由boolean标志变量save_it

请注意,由于我们要处理所有对, std::adjacent_find谓词总是返回false 这是这个解决方案的骇人听闻的部分。

迄今为止的解决方案尝试直接使用std::vector迭代器作为算法的输入。 如何定义一个返回std::pair并且步幅为 2 的自定义迭代器? 然后创建对的向量是使用std::copy的单线。 迭代器以对的形式有效地提供了原始向量的“视图”。 这也允许使用许多标准算法。 下面的例子也可以推广到与大多数容器迭代器一起工作,即你做了一次定义这样一个迭代器的困难工作,然后你可以将它应用于各种容器和算法。 现场示例: https ://godbolt.org/z/ceEsvKhzd

#include <vector>
#include <algorithm>
#include <iostream>
#include <cassert>

struct pair_iterator {
    using difference_type = std::vector<int>::const_iterator::difference_type;
    using value_type = std::pair<int, int>;
    using pointer = value_type*;
    using reference = value_type; // Not a pair&, but that is ok for LegacyIterator
    // Can't be forward_iterator_tag because "reference" is not a pair&
    using iterator_category = std::input_iterator_tag;

    reference operator*()const { return {*base_iter, *(base_iter + 1)}; }
    pair_iterator & operator++() { base_iter += 2; return *this; }
    pair_iterator operator++(int) { auto ret = *this; ++(*this); return ret; }

    friend bool operator==(pair_iterator lhs, pair_iterator rhs){
        return lhs.base_iter == rhs.base_iter;
    }

    friend bool operator!=(pair_iterator lhs, pair_iterator rhs){
        return lhs.base_iter != rhs.base_iter;
    }

    std::vector<int>::const_iterator base_iter{};
};

auto pair_begin(std::vector<int> const & v){ assert(v.size()%2==0); return pair_iterator{v.begin()}; }
auto pair_end(std::vector<int> const & v){ assert(v.size()%2==0); return pair_iterator{v.end()}; }

int main()
{
    std::vector<int> values = {1,2,3,4,5,6,7,8,9,10};
    std::vector<std::pair<int, int>> pair_values;

    std::copy(pair_begin(values), pair_end(values), std::back_inserter(pair_values));

    for (auto const & pair : pair_values) {
        std::cout << "{" << pair.first << "," << pair.second << "} ";
    }
    std::cout << std::endl;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM