我有一个偶数长度的一维向量,我想将其转换为一个二维向量,其中每个元素都包含两个数字。我知道可以使用简单的循环来实现这个目标,但我想知道是否有一个不错的标准库工具可以做到这一点?可以假定原始向量始终包含偶数个元素。
示例:
vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
vector<pair<int, int>> goal { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
我有一个偶数长度的一维向量,我想将其转换为一个二维向量,其中每个元素都包含两个数字。我知道可以使用简单的循环来实现这个目标,但我想知道是否有一个不错的标准库工具可以做到这一点?可以假定原始向量始终包含偶数个元素。
示例:
vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
vector<pair<int, int>> goal { {1, 2}, {3, 4}, {5, 6}, {7, 8} };
使用 Range-v3 库:
#include <range/v3/range/conversion.hpp>
#include <range/v3/view/transform.hpp>
#include <range/v3/view/chunk.hpp>
using namespace ranges;
using namespace ranges::views;
int main() {
std::vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
std::vector<std::pair<int, int>> goal {{1, 2}, {3, 4}, {5, 6}, {7, 8}};
auto constexpr makePairFromRangeOf2 = [](auto two){
return std::make_pair(two.front(), two.back());
};
auto result = origin | chunk(2)
| transform(makePairFromRangeOf2)
| to_vector;
}
注意,如果你只需要遍历 result
,那么它只需要是一个范围(range),所以你可以省略 | to_vector
,因为你仍然可以使用 result.begin()
和 result.end()
,这就是使得 result
成为一个范围的原因。
如果你不需要内部容器真正成为 std::pair
,但是满足于调用例如 result.front().front()
而不是 result.front().first
,那么你也可以省略 transform
,并且使用 auto result = origin | chunk(2);
即可。
你没有提到为什么只想要一个标准解决方案。不过请注意,<ranges>
在 C++20 中已经成为标准。不幸的是,该功能在 C++20 中并不像之前的 Range-v3 库一样强大。但我认为在某个时候(C++23?)将会达到同样的水平,这一点毫无疑问。
ranges::to
和views::chunk
,因此这实际上是“标准”答案。 - 康桓瑋chunk()
视图:std::vector origin = {1, 2, 3, 4, 5, 6, 7, 8};
auto goal = origin | ranges::views::chunk(2) | ranges::to<std::vector>;
在 GodBolt 上查看它的工作。
与我的 其他答案 不同,这将在语言上是完全有效的。
注意事项:
chunk()
和to()
将出现在C++23中,所以除了对标准库包含的轻微语法调整(例如添加std::
),这将是一个有效的C++23。goal
的元素不是std::pair
,而是ranges。您需要获取第一个和第二个或第一个和最后一个元素来创建一个实际的pair。C ++ 20
也不行,但是将在尚未发布的C ++ 23
中使用Range-v3
库。#include <iostream>
#include <vector>
// time complexity: O(n / 2), where `n` is the length of `my_vec`
std::vector<std::pair<int, int>> vec_to_pair(const std::vector<int> &my_vec, int odd_origin)
{
std::vector<std::pair<int, int>> val;
for (std::size_t i = 0; i < my_vec.size(); i += 2)
{
int sec_val;
if (i < my_vec.size() - 1)
sec_val = my_vec[i + 1];
else if (my_vec.size() % 2 != 0)
sec_val = odd_origin;
else
break;
int data[] = {my_vec[i], sec_val};
val.push_back({data[0], data[1]});
}
return val;
}
void print(const std::vector<std::pair<int, int>> &vec)
{
std::cout << "{ ";
for (auto &&i : vec)
std::cout << "{ " << i.first << ", " << i.second << " } ";
std::cout << " }" << std::endl;
}
int main(void)
{
std::vector<int> vec1 = {1, 2, 3, 4, 5}; // odd
std::vector<int> vec2 = {1, 2, 3, 4, 5, 6}; // even
auto x1 = vec_to_pair(vec1, -1);
auto x2 = vec_to_pair(vec2, 0);
print(x1);
print(x2);
return 0;
}
在不使用range v3库的情况下实现这一点的简单方法:
template <typename T>
std::vector<std::pair<T, T>> windowed(const std::vector<T> &vec) {
const size_t size = vec.size();
if (size % 2 != 0) {
throw std::exception("Vector does not contain an even amount of elements!");
}
std::vector<std::pair<T, T>> result;
for (size_t i = 0; i < size; i = i + 2) {
const T &left = vec.at(i);
const T &right = vec.at(i + 1);
result.emplace_back(left, right);
}
return result;
}
有一种快速而粗暴的方法,可能会实现你要求的功能,但它甚至不会复制数据……缺点是你不能确定它能否正常工作。这依赖于未定义行为,因此不能推荐使用。我描述它是因为我相信这是人们直觉上认为我们可以做到的。
所以,关键在于使用std::span
来重新解释向量数据:
std::vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};
auto raw_data = reinterpret_cast<std::pair<int, int>*>(origin.data());
std::span<std::pair<int, int>> goal { raw_data, origin.size()/2 };
在 GodBolt 上查看此内容。
注意事项:
reinterpret_cast
是“肮脏”的。它会导致未定义的行为,实际上它的作用取决于编译器和平台。如果你也忘记了 std::vector
和 std::pair
,而是使用一个二维的 std::mdspan
作为结果,那么你可以规避这个问题。然而 - std::mdspan
只在 C++20 中进入语言。std::span
。在 C++20 之前,你仍然可以使用 span(和 mdspan),但需要使用(流行的)独立库。goal.data()
处有两倍于 int 大小的值可能会成为一个问题。std::pair
不能和连续的 int
指针别名。 - Konrad Rudolphreinterpret_cast
有何直觉性。 - rubenvb
for
循环就可以完成任务。为什么要把事情搞得那么复杂(就像提出的答案一样)呢?让事情保持简单不好吗? - Fareanor