【问题标题】:for_each that gives two (or n) adjacent elementsfor_each 给出两个(或 n 个)相邻元素
【发布时间】:2013-11-12 11:10:03
【问题描述】:

是否有 for_each 的标准实现,它使用元素和范围内的下一个元素进行调用?

例如取范围{0, 1, 2, 3, 4, 5},我想调用一个函数f,每个元素及其后继:{f(0, 1), f(1, 2), f(2, 3), f(3, 4), f(4, 5)} 注意最后一个元素是如何被遗漏的,因为它没有后继者。

如果能将其推广到与元素本身一起传递的 n 个后继者,那就太好了。

到目前为止,我一直通过带有迭代器的手写循环来解决这个问题。 但是,我想更多地遵循基于 for 或 std::for_each 的 C++11 范围,以避免样板代码。

示例

// today: handwritten loop
for(Range::iterator current = range.begin(); current != range.end(); ++current) 
   f(*current, *std::next(current));

// near future: wrapped version
for_each_pair(range.begin(), range.end(), f);

// slightly further future: generalized version
for_each_tuple<n>(range.begin(), range.end(), f);

其他问题

函数的名称可以改进。对我来说for_each_pair/tuple 听起来应该返回范围内所有大小为 n 的子集(这本身就是我想解决的另一个问题)。所以我想要一些更好的名字的建议,比如:

for_each_adjacent<n>

临时解决方案

我已经在CR 上发布了my own solution。我不会在这里重复它,因为这是一个标准解决方案,并且已经有足够的自己动手的答案。

【问题讨论】:

  • 对 C++ 的&lt;algorithms&gt; 的快速浏览显示没有证据表明存在这种野兽,但这不是我第一次错过某些东西。您继续编写样板代码而不是将基于迭代器的解决方案模板化并将其放入您的工具箱中的任何特殊原因?
  • @WhozCraig:到目前为止,只有必须有一个标准实现(std 或 boost)的唠叨感觉和纯粹的懒惰让我无法完成我自己的版本。
  • 啊,我不能对 boost 发表评论,因为我从未使用过它(被依赖于其序列化算法的商业产品严重咬伤,该算法发生了变化;终身禁止工作)。看来您确实有能力做到这一点,甚至考虑过扩展(作为通用解决方案的元组方法是一个很好的方法)。您可能需要在一个下午打扫灰尘并将其塞住。 +1 有趣的问题,顺便说一句。
  • 提醒一下:我在CR 上发布了一些与此相关的代码。当它更完善时,我可能会将其作为答案发布在这里。

标签: c++ algorithm foreach


【解决方案1】:

您实际上可以为此滥用std::uniquestd::adjacent_find:谓词在迭代器范围内的每个连续对中调用,并且只要谓词始终返回false,它就不会修改任何内容或提前返回。

忽略那个特定的 hack,我会将其实现为迭代器适配器,而不是算法。也就是说,我将实现一个consecutive_tuple_iterator&lt;N&gt;,它将返回 N 个连续项目的所有元组。然后你可以将它用于count_ifincludes 之类的东西,而不仅仅是for_each。 (不过,它不适用于大多数修改算法。)

【讨论】:

  • 虽然它可能会起作用,但该 hack 将依赖于上述两种算法的内部实现,这不是一个好主意。你对迭代器的看法是对的,我还没有遇到过其他用途,但这主要是因为到目前为止我有 for_each 的样板。
  • adjacent_find 的情况下不依赖实现细节;该算法的行为由标准严格定义。
  • 我会使用向前跳过的迭代器适配器进行第一遍。然后修改它以返回一个元组,并调整end 检测。
【解决方案2】:

最简单的方法是将其编写为通用算法,然后多次应用。

 template< typename FwdIter, typename Func >
 Func for_each_pair( FwdIter iterStart, FwdIter iterEnd, Func func )
 {
     if( iterStart == iterEnd )
        return func;

     FwdIter iterNext = iterStart;
     ++iterNext;

     for( ; iterNext != iterEnd; ++iterStart, ++iterNext )
     {
          func( *iterStart, *iterNext );
     }
     return func;
}

当我被问到为什么它返回 func(而不是 void)时,这是 for_each 的典型特征,因为

  1. func 可以是一个对象
  2. 按值传递。

func 可能“累积”某种状态,但累积它的是我们在该算法中制作的副本,而不是用户的原始对象。因此,我们将它们传回修改后的“func”对象。

【讨论】:

  • 为什么返回func
  • 因为 func 不必是一个实际的函数,而可以是一个对象,它可以保持状态,并且这些通常会被复制。传入的 func 对象已被复制,因此不会收到新状态,因此我们将其返回,并进行任何修改
  • 在这种情况下,我认为最好写一个std::accumulate-alike 函数,它更具表现力。如果函数状态(其结果类型)很复杂,只需根据需要提供运算符即可。
  • 另一种方法是制作一个特殊的迭代器,然后将其应用于任何算法。您将使用 make_pair_iter_begin 和 make_pair_iter_end 为集合创建它,并可能为随机前向迭代器创建一个。
【解决方案3】:

使用 C++ 11 和新的迭代器辅助函数 std::nextstd::prev 用于迭代器,标准算法 std::transform 的第二个变体可用于迭代相邻元素。

这是一个从列表中生成相邻对列表的示例:

std::vector<int> nums{3, 4, 2, 9, 15, 267};
std::vector<std::pair<int,int>> num_pairs;
if (!nums.empty()) {
    std::transform(
        std::begin(nums), std::prev(std::end(nums)),
        std::next(std::begin(nums)),
        std::back_inserter(num_pairs),
        std::make_pair<
            decltype(nums)::const_reference,
            decltype(nums)::const_reference
        >
    );
}

【讨论】:

  • 这在 C++03 中也是可能的,只需多一点代码。然而,它只解决了两个相邻节点的问题,并且对每个节点都做的不仅仅是一个简单的问题(可以通过提供一个虚拟迭代器来解决这个问题)。
【解决方案4】:

不完全是你想要的,但是看看cpplinq

int numbers[] = {0, 1, 2, 3, 4, 5};
auto pairs = cpplinq::from_array(numbers) 
          >> cpplinq::pairwise() 
          >> cpplinq::to_vector(); // yields (0,1), (1,2), (2,3), (3,4), (4,5)

for(auto p : pairs)
    f(p.first, p.second);

【讨论】:

  • 我没有使用 cpplinq 的经验(这使得它对我来说“非标准”),但这似乎会创建一个包含元素的临时向量,这是一个很大的开销。
猜你喜欢
  • 2019-09-02
  • 1970-01-01
  • 1970-01-01
  • 2018-05-03
  • 2013-03-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-05-27
相关资源
最近更新 更多