是否有标准算法可以复制直到某个条件满足?

4
我正在使用一个 istream_iterator<char> it,因此无法对范围进行反向迭代(或两次迭代它,而不需要大量的麻烦)。
我想复制直到满足条件。标准库中是否有类似这样的函数:
copy_until(it, istream_iterator<char>(), ostream_iterator<char>(cout), [](const unsigned char i){ return isalpha(i); })

如果我必须滚动某些内容,我可以这样做,我只是希望能找到一些我还没有想出的魔法。

编辑:

我期望从我的虚构的 copy_until 函数中得到以下行为:

while(it != istream_iterator<char>()) {
    if(!isalpha(static_cast<unsigned char>(*it))) break;
    cout << *it++;
}

2
你是指像copy_if这样的函数吗?还是类似于find算法,它可以找到满足条件的元素之后的一个位置,以便你可以复制一段范围? - EdChum
@EdChum 不,即使条件不成立,copy_if 仍然会继续复制。我想要的是在条件被违反时停止复制,我已经进行了编辑以澄清。 - Jonathan Mee
2
仅限于术语,这个问题适用于所有的输入迭代器,其中std::istream_iterator<char>就是一个例子。 - Pete Becker
@PeteBecker 是的,我最初只写了输入迭代器,但回答表明即使有一个具体的输入迭代器示例,人们仍然误解了问题 :( 如果您认为这会有帮助,我可以将其编辑回问题中。 - Jonathan Mee
@JonathanMee -- 不需要更改,只是添加一些信息。 - Pete Becker
4个回答

8

为了完整起见,由于标准没有提供现成的解决方案,这是我的解决方案:

template<class _InIt, class _OutIt, class _Pr>
inline void copy_until (_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred) {
  while ((_First != _Last) && _Pred(*_First)) {
    *_Dest++ = *_First++;
  }
}

这是我如何使用它的:

copy_until(std::istreambuf_iterator<char>(is),
           std::istreambuf_iterator<char>(),
           std::ostreambuf_iterator<char>(os), 
           [] (char c) { return <some usefull condition here> });

例如,要从输入流中读取仅包含字母数字字符的字符串:
std::istream& operator>> (std::istream& is, std::string& n) {
  std::ostringstream str;
  copy_until(std::istreambuf_iterator<char>(is),
             std::istreambuf_iterator<char>(),
             std::ostreambuf_iterator<char>(str), 
             std::isalnum);
  n = str.str();
  return is;
}

还不错...我认为我仍然会选择while循环,因为它更清晰易懂,但这也是可用的。 - Jonathan Mee

3

除非出现问题,否则没有复制。由于您正在从流中复制,因此别无选择,只能使用带有break语句的循环。


无法与istream_iterator一起使用,这正是问题的重点。 - Jonathan Mee
@JonathanMee,哦,你在模拟函数中颠倒了copy参数。我以为你是从它复制到cout,就像copy参数一样。我建议你改变参数以避免混淆。在这种情况下,只需使用循环即可。 - SergeyA
我不确定我理解你的意思,合法范围是 [itistream_iterator<char>()) 这是我的前两个参数,输出迭代器是 ostream_iterator<char>(cout) ,这是我的第三个参数,接着是我的 lambda 表达式。这遵循了 copy_if 函数或任何条件复制算法的定义。 - Jonathan Mee
@JonathanMee,好的,我又读错了。你是从某个istream复制到cout。明白了。 - SergeyA
嘿,看起来这个问题的误解很普遍。它似乎应该有一些标准来做到这一点。 - Jonathan Mee

1

http://en.cppreference.com/w/cpp/algorithm 提供了一个非常有用的参考,列出了C++中所有可用的算法(不仅限于Algorithm Library中的算法,还包括NumericMemoryCStd Libraries)。其中以下是复制算法,它们接受输入迭代器、输出迭代器和lambda作为参数:

  • copy_if "复制由 [first, last) 定义的范围内的元素... 仅复制谓词 pred 返回 true 的元素"
  • transform "对一个范围应用给定的函数,并将结果存储在另一个范围中。"
  • remove_copy_if "从范围 [first, last) 复制元素到以 d_first 开始的另一个范围,省略满足特定条件的元素"
  • replace_copy_if "将范围 [first, last) 中的所有元素复制到以 d_first 开始的另一个范围,并使用 new_value 替换满足特定条件的所有元素"
  • unique_copy "将范围 [first, last) 中的元素复制到以 d_first 开始的另一个范围中,使得没有连续相等的元素... 元素使用给定的二元谓词 p 进行比较"
  • partition_copy "将范围 [first, last) 中的元素复制到两个不同的范围中,具体取决于谓词 p 返回的值。满足谓词 p 的元素被复制到以 d_first_true 开始的范围中。其余的元素被复制到以 d_first_false 开始的范围中"
  • merge 需要第二个输入范围
  • set_difference 需要第二个输入范围
  • set_intersection 需要第二个输入范围
  • set_symmetric_difference 需要第二个输入范围
  • set_union 需要第二个输入范围
  • adjacent_difference "计算范围 [first, last) 中每个相邻元素对的第二个和第一个之间的差异... 使用给定的二元函数 op 计算差异"
  • partial_sum "计算范围 [first, last) 中子范围中元素的部分和,并将其写入以 d_first 开始的范围中... 为了对元素求和,第二个版本使用给定的二元函数 op。"
  • exclusive_scan "使用 binary_op 计算范围 [first, last) 的独占前缀和操作"
  • inclusive_scan "使用 binary_op 计算范围 [first, last) 的包含前缀和操作"
  • transform_exclusive_scan "使用 unary_op 因为lambda仅用于修改范围[first,last)分配给d_first的1:1赋值; transformreplace_copy_if和所有Numeric Library算法都没有帮助(adjacent_differencepartial_sumexclusive_scaninclusive_scantransform_exclusive_scantransform_inclusive_scan)。
    如果在 lambda 条件满足后,范围 [it, istream_iterator<char>()) 的余下部分需要直接复制到第二个输出迭代器,则 partition_copy 可以解决您的问题。
    如果在 lambda 条件满足后,范围 [it, istream_iterator<char>()) 的余下部分需要被函数迭代,可以使用 copy_if(或 remove_copy_ifunique_copy)在满足条件后对每个值调用该函数。
    但是,在一般情况下,回答您的问题是标准算法不提供“copy_until”,因此您需要使用您的 while 循环。

-1

copy_until 可以使用 std::find_ifstd::copy 实现。

使用与 redleg 相同的结构,算法可以先搜索终止符,然后复制范围。

template <class _InIt, class _OutIt, class _Pr>
inline void copy_until(_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred) {
    _InIt _posTerm = std::find_if(_First, _Last, _Pred);
    std::copy(_First, _posTerm, _Dest);
}

这个模板需要包含 <algorithm><iterator>

以下是一个使用示例:

std::vector<int> source = {1,2,3,4,5,6,7,8};
std::vector<int> dest {};

copy_until(source.begin(),source.end(), 
           back_inserter(dest),[](int c) {return c == 5;});

输出结果为:

source: 1 2 3 4 5 6 7 8 
dest  : 1 2 3 4 

因为 std::copy 的定义是复制到最后,但不包括最后一个元素,或者说是 [first,last) 的位置,所以 find_if 的位置不会被复制。
如果需要一个包含最后一个元素的区间,即 [first,last],我们需要进行更多的检查。如果终止符在 end() 的位置,迭代器就不应该被推进。
template <class _InIt, class _OutIt, class _Pr>
inline void copy_until(_InIt _First, _InIt _Last, _OutIt _Dest, _Pr _Pred)
{
    _InIt _posTerm = std::find_if(_First, _Last, _Pred);
    if (_posTerm != _Last ) { _posTerm++; }
    std::copy(_First,_posTerm,_Dest);
}

前面的例子现在将输出:

source: 1 2 3 4 5 6 7 8 
dest  : 1 2 3 4 5

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