如何获取std::list的前N个元素?

3
什么是获取std :: list的前N个元素或整个列表(如果N> =列表大小)的正确且安全的方法(并将N = 0作为一种处理方式)?
更新:
实际上,我不一定需要一个新列表,我只想在后续代码中操作列表的子集。我假设创建一个新列表是这样做的合理方式(请注意,列表大小通常不超过50)。

1
很可能你不想使用std::list来处理输入或结果。在大多数情况下,使用std::vector会使生活变得更加简单。大多数算法都适用于定义范围,这将允许你在正确的子集上操作而无需进行任何复制。 - Jerry Coffin
@JerryCoffin 在取子集之前,我需要对列表进行排序,这会影响使用向量的决定吗? - User
1
是的 - 这使得向量成为更好的选择。如果你只想要前N个最大(或最小)的元素,你可以使用 std::nth_element 来获取它们。 - Jerry Coffin
4个回答

9
std::list<int> a;
size_t n = 13;
auto end = std::next(a.begin(), std::min(n, a.size()));

创建一个新的列表,其中包含第一个列表的前n个元素:

std::list<int> b(a.begin(), end);

或者填充现有列表:

std::list<int> b;
std::copy(a.begin(), end, std::back_inserter(b));

2
我查了一下,不认为 std::advance 会处理越过末尾的情况。至少标准只是将其视为迭代器前进,没有提及检查这种情况。 - chris
为什么你不能简单地执行a.begin() + n呢? - David G
1
@0x499602D2,它是一个双向迭代器。std::next 无论如何都可以工作。 - chris
由于 OP 提到了“新列表”,使用两个迭代器构造函数构建 b 可能更有意义。 - juanchopanza
2
你为什么要用不支持列表迭代器的 operator+ 替换掉能够正常工作的 std::next,导致你的代码出现了问题? - Benjamin Lindley
@BenjaminLindley 闲着无聊。已修复。 - David

6
template<typename T>
std::list<T> first_n(const std::list<T> &in, std::size_t n) {
    return std::list<T> out{in.begin(),
      std::next(in.begin(), std::min(in.size(), n))};
}

为了“返回”一个列表,我总是将空列表作为参数传递给函数。你在这里的做法是否有比返回参数更多的成本影响?作为一名C#程序员,我更喜欢这种方式,但我读到的大部分建议不要通过返回语句返回容器。 - User
1
@User 请查看https://dev59.com/iHRA5IYBdhLWcg3w_DLF - 在所有现代编译器中,返回容器与使用输出参数一样高效。 - ecatmur

4
// list<int> input;
list<int> output;
for (list<int>::const_iterator i = input.begin(); i != input.end() && N > 0; ++i, --N)
    output.push_back(*i);

1
我猜没有其他人认为当你可以像这样轻松避免时,迭代两次列表是愚蠢的低效率。 - Benjamin Lindley
@BenjaminLindley:你是什么意思?你是说这个答案比其他答案更好还是不如其他答案?其他答案会导致列表被迭代两次吗? - User
3
@User:是的,我想说这个答案可能更加高效,尽管提升并不会太多。其他答案使用std::next来找到用于初始化第二个列表的末尾迭代器。对于像std::vectorstd::deque这样的容器来说,这是一个便宜的操作,因为它们有随机访问迭代器。但是对于std::list,由于它具有双向迭代器,需要迭代遍历每个项直到N。然后当您初始化第二个列表时,需要再次迭代。 - Benjamin Lindley

0

在你的问题中提到了以下内容:

实际上,我并不一定需要一个新列表,我只想在后续代码中操作列表的子集

从C++17开始,可以使用std::for_each_n

例如,让我们计算列表中前N(4)个数字的平方。

示例1:就地修改:

    std::list<int> nums{ 1,2,3,4,5,6,7,8 };

    //MAKE NOTE OF SENDING ARGUMENT AS A REFERENCE (int& num)
    std::for_each_n(nums.begin(), 4, [](int& num) { num = num * num; });

    //PRINT
    for (auto n : nums)
        std::cout << n << "  ";

示例2:修改并将它们放入不同的列表中:

    std::list<int> nums2{ 1,2,3,4,5,6,7,8 };
    std::list<int> newNums;

    //MAKE NOTE OF CAPTURE [&}, newNums EXPOSED INSIDE LAMBDA.
    std::for_each_n(nums2.begin(), 4, [&](int num) {newNums.push_back(num * num); });
    
    //PRINT
    for (auto n : newNums)
        std::cout << n << "  ";

还有一种过载可用于指定执行策略,需要包含“execution”头文件。因此,下面的代码将使用并行执行策略执行,对于处理大型列表非常有用。
std::for_each_n(std::execution::par,nums.begin(), 4, [](int& num) { num = num * num; });

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