在C++中从单个向量创建一组配对向量

25

我有一个偶数长度的一维向量,我想将其转换为一个二维向量,其中每个元素都包含两个数字。我知道可以使用简单的循环来实现这个目标,但我想知道是否有一个不错的标准库工具可以做到这一点?可以假定原始向量始终包含偶数个元素。

示例:

vector<int> origin {1, 2, 3, 4, 5, 6, 7, 8};

vector<pair<int, int>> goal { {1, 2}, {3, 4}, {5, 6}, {7, 8} };

1
@Schottky 哦,现在我明白了。 - Darth-CodeX
如果你创建了一个前进两次的迭代器,那么你可以很容易地使用std::transform。 - Taekahn
@Taekahn:但是楼主正在寻找现有的工具,而不是自己编写的东西。 - einpoklum
@einpoklum 上次我检查时,迭代器和转换是现有的标准工具。 - Taekahn
4
需要将这个简单任务变成标准工具放在货架上吗?正如你在这里所看到的,一个简单的for循环就可以完成任务。为什么要把事情搞得那么复杂(就像提出的答案一样)呢?让事情保持简单不好吗? - Fareanor
显示剩余2条评论
5个回答

21

使用 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?)将会达到同样的水平,这一点毫无疑问。


1
OP 要求一个“标准库工具”。 - einpoklum
嗯,而且你也不能使用std::ranges。 - Alan Birtles
7
事实上,C++23已经采用了ranges::toviews::chunk,因此这实际上是“标准”答案。 - 康桓瑋
4
这是一个在线编译器的链接,可以在其中输入C++代码并查看其汇编语言。您可以通过单击不同的选项来更改编译器和其他设置,并查看生成的汇编代码。 - Marek R
@Darth-CodeX,我喜欢你的回答并已点赞。但是,我明确要求任何我不必自己编写的东西,因为它可能已经存在。 - Schottky
显示剩余2条评论

13
根据 @康桓瑋 的提到,如果你愿意使用 ranges-v3 库,你可以使用 chunk() 视图:
std::vector origin = {1, 2, 3, 4, 5, 6, 7, 8};
auto goal = origin | ranges::views::chunk(2) | ranges::to<std::vector>;

GodBolt 上查看它的工作。

与我的 其他答案 不同,这将在语言上是完全有效的。

注意事项:

  • 这将复制您的数据!
  • 很可能会引入一些东西(错误字符串、异常处理程序等)到您的目标代码中。
  • 使用ranges库会显著增加编译时间(尽管启用C++20可能减少一些?)
  • 不基于标准库 - 但显然chunk()to()将出现在C++23中,所以除了对标准库包含的轻微语法调整(例如添加std::),这将是一个有效的C++23。
  • goal的元素不是std::pair,而是ranges。您需要获取第一个和第二个或第一个和最后一个元素来创建一个实际的pair。

1
输出结果与期望的类型不同:https://godbolt.org/z/fYz6fGMb7,缺少 transform。 - Marek R
@MarekR:在我的警告中已经提到了。 - einpoklum

6
我有一个函数可以处理向量中的偶数和奇数个元素。它会接受另一个参数来在一对数的末尾添加一个数字。我认为目前没有任何标准的工具/库可以做到这一点,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;
}

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;
}

-9

直觉上的,但不幸无效的方法

有一种快速而粗暴的方法,可能会实现你要求的功能,但它甚至不会复制数据……缺点是你不能确定它能否正常工作。这依赖于未定义行为,因此不能推荐使用。我描述它是因为我相信这是人们直觉上认为我们可以做到的。

所以,关键在于使用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::vectorstd::pair,而是使用一个二维的 std::mdspan 作为结果,那么你可以规避这个问题。然而 - std::mdspan 只在 C++20 中进入语言。
  • 说到语言标准,这是 C++20,因为有了 std::span。在 C++20 之前,你仍然可以使用 span(和 mdspan),但需要使用(流行的)独立库。
  • 具体来说,如果你在一个不允许非对齐访问的平台上,坚持在 goal.data() 处有两倍于 int 大小的值可能会成为一个问题。

4
警告不应该是它“经常”导致未定义的行为,而应该是它在这里确实会导致未定义的行为。这段代码是非法的:一个 std::pair 不能和连续的 int 指针别名。 - Konrad Rudolph
2
@KonradRudolph:重新表述一下,“非法”这个词也不太合适。C++警察不会因此逮捕你,让你接受审判。 - einpoklum
4
考虑到C++的现状,我认为建议使用未定义行为是不合理的。 - Passer By
5
“非法”一词通常用于描述违反标准的C++代码,特别是表现出未定义行为的代码,这是一个完全合适的描述。我没有对这个回答进行投票,但我也认为这不是一个好的回答:一般情况下,未定义行为是不可以的。它可能在非常具体的情况下是可以的,比如你非常了解编译器和平台。但一般情况下并不是这样的。我几乎从来都不够了解编译器/平台,无法自信地依赖未定义行为而不出错。 - Konrad Rudolph
3
我不理解reinterpret_cast有何直觉性。 - rubenvb
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接