【问题标题】:std::copy n elements or to the endstd::copy n 个元素或到最后
【发布时间】:2014-09-30 11:02:59
【问题描述】:

我想复制最多 N 个元素。

template< class InputIt, class Size, class OutputIt>
OutputIt myCopy_n(InputIt first, InputIt last, Size count, OutputIt result)
{
    Size c = count;
    while (first != last && c > 0) {
        *result++ = *first++;
        --c;
    }
    return result;
}

有没有办法用 std 函数做到这一点?我也可以:

template< class InputIt, class Size, class OutputIt>
OutputIt myCopy_n(InputIt first, InputIt last, Size count, OutputIt result)
{
    if(std::distance(first, last) > count)
        return std::copy_n(first,count,result);
    return std::copy(first,last,result);
}

但是,除了麻烦之外,它还会超出范围两次(距离,复制)。如果我使用的是转换迭代器或过滤器迭代器,那么这些是对我的过滤器/转换函数的 O(N) 不必要的调用。

template <class InputIt, class OutputIt>
OutputIt copy_n_max(InputIt begin, InputIt end, OutputIt last, size_t count)
{
    return std::copy_if(begin, end, last, 
                        [&count](typename std::iterator_traits<InputIt>::reference)
                        { return count--> 0; });
}

int main()
{
    std::vector<int> v({1,2,3,4,5,6,7,8,9}), out;
    copy_n_max(v.begin(), v.end(), std::back_inserter(out), 40);
    for(int i : out) std::cout <<i << " ,";
}

输出 1,2,3,4,5,6,7,8,9,

但是,这将持续到结束,并且不计算次数。尽管如此,对我的过滤器/转换函数的更多不必要的调用......

【问题讨论】:

  • 您能更具体地说明您要做什么吗?
  • @remyabel 这对我来说似乎很具体。
  • 老实说,您的第一个解决方案似乎是最直接的;将其称为 copy_upto_n() 之类的名称,然后一天结束;与第三种解决方案不同,它只遍历范围一次(最少的约束),并且它永远不会超过计算输入的取消引用。实际上,我很惊讶 copy_n 一开始就没有那样做……
  • 喜欢@Stefan,我最喜欢你的myCopy_n。碰巧,这几乎就是我今天在stackoverflow.com/questions/54254075/… 上建议的内容。其他解决方案似乎过于复杂和/或过于受限。亲吻。
  • @Stefan:copy_n 不能这样做,因为它没有传递结束迭代器。

标签: c++ stl-algorithm


【解决方案1】:

如果您可以访问整个数据结构及其大小,则可以使用以下内容:

std::vector<int> v1, v2;
std::copy_n(v2.begin(), std::min(NUM, v2.size()), std::back_inserter(v1));

如果您只能访问迭代器,我不知道如何只使用 std 函数而不计算距离。这对于随机访问迭代器来说很便宜,但重复对于其他类型也有效。

std::vector<int>::iterator i_begin, i_end, o_begin;
std::copy_n(i_begin, std::min(NUM, std::distance(i_begin, i_end)), o_begin);

【讨论】:

  • 是的,您的方式不那么繁琐,但运行时相同。我想这是我们能得到的最好的。如果过滤/转换功能可以忽略不计,则必须自己复制或为“距离”付出代价。 copy_n 它受到结束迭代器的保护,这似乎很可惜。
  • @user2232888 std::distance 的复杂性对于随机访问迭代器是恒定的。所以要么std::distance 是免费的,要么你事先知道容器的大小,Neil Kirk 的第一个解决方案就可以了。
  • Johathan Mee,当你只有迭代器时,这是不正确的。如果您使用过滤器/转换迭代器,调用 ++ 可能会很昂贵...
  • @user2232888 如果你只有非随机访问迭代器并且性能很关键,我会使用我自己的函数。
【解决方案2】:

我会选择这样的:

template <class InputIt, class OutputIt>
OutputIt copy_n_max(InputIt begin, InputIt end, OutputIt last, size_t count)
{
    return std::copy_if(begin, 
                        end, 
                        last, 
                        [&count](typename std::iterator_traits<InputIt>::reference) -> bool 
                        {
                            if (count > 0)
                            {
                                --count;
                                return true;
                            }
                            return false;
                        });
}

使用copy_if 谓词检查是否复制了足够的输入。 我在这里看到的主要优势是不涉及额外的std::distance

Live example on IDEOne

【讨论】:

  • 我很确定编译器会简化它。而且我觉得它更具可读性。
  • decltype(*begin)&amp; => 不,不是真的。你想要的是typename std::iterator_traits&lt;InputIt&gt;::reference,这样它就可以与代理 一起工作(就像在std::vector&lt;bool&gt; 中一样)。在 C++14 中,您也可以使用 auto&amp;,并且不要回头。
  • @MatthieuM。我不明白declatype(*begin)&amp; 的问题你能告诉我一个它不起作用的例子吗?
  • 这不会消耗整个输入迭代器吗?
  • @Johan 如果是stdin,例如,那将丢失数据。
【解决方案3】:

有一种简单的方法可以使用 C++11 为您的任务添加的std::copy_if-overload(仅需要 InputIterators):

template< class InputIt, class Size, class OutputIt>
OutputIt myCopy_n(InputIt first, InputIt last, Size count, OutputIt result)
{
    return std::copy_if(first, last, result,
        [&](typename std::iterator_traits<InputIt>::reference)
        {return count && count--;});
}

顺便说一句:在 C++14 中它变得更好,不需要变量或如此复杂的参数:

std::copy_if(first, last, result,
    [count = some_complicated_expression](auto&&) mutable
    {return count && count--;});

【讨论】:

  • count 大于范围大小时,唯一的烦恼是不必要的检查。
  • 嗯,是的,可以添加一些模板,其中包含标准库中使用的所有优化和快捷方式。我认为std::copy_n 的重载应该是可行的。
  • @Deduplicator:啊!我完全掩盖了这个事实!事实上,它是copy_if 而不是copy_until
  • 你为什么要返回 count && count-- ?为什么不只是计数-> 0?我将不得不研究这个 [count = some_complicated_expression]。这东西有名字吗?
  • @user2232888 我要返回那个,因为我不知道 count 是有符号还是无符号。甚至可能使提前返回的可能性更大,尽管不知道。这是一个初始化程序,需要使用例如移动语义和 C++14 的一部分。
【解决方案4】:

您可以将copy_if 与自定义谓词一起使用,它适用于旧版本的 c++。

#include <algorithm>
#include <iostream>
#include <vector>
#include <iterator>


struct LimitTo
{
    LimitTo( const int n_ ) : n(n_)
    {}

    template< typename T >
    bool operator()( const T& )
    {
        return n-->0;
    }

    int n;
};

int main()
{
    std::vector< int > v1{ 1,2,3,4,5,6,7,8 };
    std::vector< int > v2;

    std::copy_if( std::begin(v1), std::end(v1), std::back_inserter(v2), LimitTo(3) );

    std::copy( std::begin(v1), std::end(v1), std::ostream_iterator<int>(std::cout,", ") );
    std::cout << std::endl;
    std::copy( std::begin(v2), std::end(v2), std::ostream_iterator<int>(std::cout,", ") );
    std::cout << std::endl;
}

此示例复制 n 个元素,使用谓词 LimitTo

【讨论】:

  • Same problem 作为使用copy_if 的其他答案,它会在达到计数后不断增加迭代器。
【解决方案5】:

从 C++20 开始,std::ranges::views::take(e, f) 很好地解决了这个问题...它在输入范围 e 的末尾终止,或者在 f 元素被迭代时终止,以先到者为准。

【讨论】:

  • 是的,新的(-er)工具很不错。
猜你喜欢
  • 2012-03-12
  • 1970-01-01
  • 2021-07-17
  • 2020-07-17
  • 1970-01-01
  • 2012-01-08
  • 1970-01-01
  • 2011-12-27
  • 2019-06-21
相关资源
最近更新 更多