"除了最后一个之外的每一个"(或"每个连续元素对之间")的成语

ein*_*ica 76 c++ idioms separator c++11

每个人都会遇到这个问题:

for(const auto& item : items) {
    cout << item << separator;
}
Run Code Online (Sandbox Code Playgroud)

...而且你得到了一个你不想要的额外分隔符.有时候它不是打印,而是执行一些其他动作,但是相同类型的连续动作需要一些分隔符动作 - 但最后一个动作不需要.

现在,如果你使用old-school for循环和数组,你会这样做

for(int i = 0; i < num_items; i++)
    cout << items[i];
    if (i < num_items - 1) { cout << separator; }
}
Run Code Online (Sandbox Code Playgroud)

(或者你可以特别说明循环中的最后一项.)如果你有任何承认非破坏性迭代器的东西,即使你不知道它的大小,你可以这样做:

for(auto it = items.cbegin(); it != items.cend(); it++) {
    cout << *it;
    if (std::next(it) != items.cend()) { cout << separator; }
}
Run Code Online (Sandbox Code Playgroud)

我不喜欢最后两个的美学,并喜欢一系列的循环.我可以获得与后两个相同的效果,但使用更多漂亮的C++ 11ish结构吗?


为了进一步扩展这个问题(除了这个之外),我会说我也不想明确地说明第一个或最后一个元素的特殊情况.这是一个"实施细节",我不想被打扰.所以,在虚构的未来C++中,可能是这样的:

for(const auto& item : items) {
    cout << item;
} and_between {
    cout << separator;
}
Run Code Online (Sandbox Code Playgroud)

Jar*_*d42 74

我的方式(没有额外的分支)是:

const auto separator = "WhatYouWantHere";
const auto* sep = "";
for(const auto& item : items) {
    std::cout << sep << item;
    sep = separator;
}
Run Code Online (Sandbox Code Playgroud)

  • 是的,它简单而聪明.我可以想象下次有这种任务时使用这种方法.但是,它的代价是引入一个额外的(可变的)变量并在每次迭代时为其赋值. (3认同)
  • 如果有人在“separator”需要是“std::string”而不是“char const*”的上下文中使用此模式,则直接移植此模式将在每次迭代中产生昂贵的复制成本。可以通过如下方式避免:还有一个空常量 `std::string`,首先将 `sep` 设为指向该常量的指针,输出 `*sep`,最后将 `sep` 设为指向下一个之前的 `separator` 的指针迭代。 (2认同)

Ben*_*igt 24

从迭代中排除结束元素是Ranges提案旨在简化的一种方式.(请注意,有更好的方法可以解决字符串连接的特定任务,从迭代中断开元素只会创建更多特殊情况,例如当集合已经为空时.)

当我们等待标准化的Ranges范例时,我们可以使用现有的ranged-for一个小帮助类来完成它.

template<typename T> struct trim_last
{
    T& inner;

    friend auto begin( const trim_last& outer )
    { using std::begin;
      return begin(outer.inner); }

    friend auto end( const trim_last& outer )
    { using std::end;
      auto e = end(outer.inner); if(e != begin(outer)) --e; return e; }
};

template<typename T> trim_last<T> skip_last( T& inner ) { return { inner }; }
Run Code Online (Sandbox Code Playgroud)

现在你可以写了

for(const auto& item : skip_last(items)) {
    cout << item << separator;
}
Run Code Online (Sandbox Code Playgroud)

演示:http://rextester.com/MFH77611

对于skip_last使用ranged-for的方法,需要一个双向迭代器,类似地,skip_first它具有一个Forward迭代器就足够了.

  • OP实际上想要一个`join`,而不是跳过最后一个元素.(只保留最后一个分隔符). (6认同)
  • @SergeyA:当然,这是有保证的,自从C首次标准化以来一直如此.请参阅**[conv.prom]**获取当前C++的措辞:"类型`bool`的prvalue可以转换为`int`类型的prvalue,其中`false`变为零,'true`变为1." (4认同)
  • @SergeyA:当然,它不是在没有尾随分隔符的情况下解决串联的唯一方法.OP有XY问题.但OP问的问题是"对于除了最后一个之外的每一个",这个问题很有意思,也很有用,这个答案提供了这个问题. (3认同)
  • @einpoklum:你能有这样的范围,对于一般更多的是用于阅读比更新...但标准委员会一个很好的理由选择允许的元素(而不是容器)写访问,并把它留给程序员选择只读访问(按值迭代变量或const引用)或写入(非const引用).我的解决方案保留了该功能. (2认同)

Dan*_*our 24

你知道Duff的设备吗?

int main() {
  int const items[] = {21, 42, 63};
  int const * item = items;
  int const * const end = items + sizeof(items) / sizeof(items[0]);
  // the device:
  switch (1) {
    case 0: do { cout << ", ";
    default: cout << *item; ++item; } while (item != end);
  }

  cout << endl << "I'm so sorry" << endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

(生活)

希望我没有毁掉每个人的一天.如果您不想要,则永远不要使用它.

(嘟))我很抱歉......


处理空容器的设备(范围):

template<typename Iterator, typename Fn1, typename Fn2>
void for_the_device(Iterator from, Iterator to, Fn1 always, Fn2 butFirst) {
  switch ((from == to) ? 1 : 2) {
    case 0:
      do {
        butFirst(*from);
    case 2:
        always(*from); ++from;
      } while (from != to);
    default: // reached directly when from == to
      break;
  }
}
Run Code Online (Sandbox Code Playgroud)

现场测试:

int main() {
  int const items[] = {21, 42, 63};
  int const * const end = items + sizeof(items) / sizeof(items[0]);
  for_the_device(items, end,
    [](auto const & i) { cout << i;},
    [](auto const & i) { cout << ", ";});
  cout << endl << "I'm (still) so sorry" << endl;
  // Now on an empty range
  for_the_device(end, end,
    [](auto const & i) { cout << i;},
    [](auto const & i) { cout << ", ";});
  cout << "Incredibly sorry." << endl;
  return 0;
}
Run Code Online (Sandbox Code Playgroud)

  • 我可以在不知道这一点的情况下度过漫长而幸福的生活.谢谢. (5认同)
  • 什么不是一个简单的`goto`? (5认同)
  • 这不是'foreach',而是为每个*元素做一些事情.循环是一个循环,大部分时间. (2认同)
  • 我_确实_知道达夫的设备,但非常感谢你表明它适用于这里。它是[一个更文明时代的优雅工具](http://tvtropes.org/pmwiki/pmwiki.php/Main/ElegantWeaponForAMoreCivilizedAge)...好吧,也许是一个不太文明的时代。 (2认同)
  • 最有趣的是布尔标志方法有效地被gcc优化为**设备**. (2认同)

Jam*_*son 13

我不知道有什么特别的习语.但是,我更喜欢第一种特殊情况,然后对剩余的项目执行操作.

#include <iostream>
#include <vector>

int main()
{
    std::vector<int> values = { 1, 2, 3, 4, 5 };

    std::cout << "\"";
    if (!values.empty())
    {
        std::cout << values[0];

        for (size_t i = 1; i < values.size(); ++i)
        {
            std::cout << ", " << values[i];
        }
    }
    std::cout << "\"\n";

    return 0;
}
Run Code Online (Sandbox Code Playgroud)

输出: "1, 2, 3, 4, 5"

  • 重要的是要注意,该循环可以写为`for(const auto&item:skip_first(values))`...以满足问题的要求. (6认同)

Mat*_*lia 13

通常我会这样做:

bool first=true;
for(const auto& item : items) {
    if(!first) cout<<separator;
    first = false;
    cout << item;
}
Run Code Online (Sandbox Code Playgroud)

  • 你需要一个if/else才能做到这一点 (2认同)
  • 首先永远不会变错 (2认同)
  • @Deduplicator,我在Clang和gcc上测试过它.gcc完全删除了这个标志 - 只需命令jmps以确保它只被调用一次,但clang按照书中的说法,在每次迭代时检查它(尽管在我的测试中注册). (2认同)

fil*_*pos 7

我喜欢简单的控制结构.

if (first == last) return;

while (true) {
  std::cout << *first;
  ++first;
  if (first == last) break;
  std::cout << separator;
}
Run Code Online (Sandbox Code Playgroud)

根据您的喜好,您可以将增量和测试放在一行中:

...
while (true) {
  std::cout << *first;
  if (++first == last) break;
  std::cout << separator;
}
Run Code Online (Sandbox Code Playgroud)


Mar*_*man 5

我不知道你可以在某个地方找到一个特殊情况...例如,Boost的String Algorithms Library有一个连接算法.如果你看一下它的实现,你会看到第一个项目的特殊情况(没有前进的分隔符),然后在每个后续元素之前添加一个分隔符.

  • 看看[ostream joiners](http://en.cppreference.com/w/cpp/experimental/ostream_joiner). (2认同)

Maa*_*ink 5

您可以定义一个函数for_each_and_join,它将两个仿函数作为参数.第一个仿函数可以处理每个元素,第二个函数与每对相邻元素一起使用:

#include <iostream>
#include <vector>

template <typename Iter, typename FEach, typename FJoin>
void for_each_and_join(Iter iter, Iter end, FEach&& feach, FJoin&& fjoin)
{
    if (iter == end)
        return;

    while (true) {
        feach(*iter);
        Iter curr = iter;
        if (++iter == end)
            return;
        fjoin(*curr, *iter);
    }
}

int main() {
    std::vector<int> values = { 1, 2, 3, 4, 5 };
    for_each_and_join(values.begin(), values.end()
    ,  [](auto v) { std::cout << v; }
    ,  [](auto, auto) { std::cout << ","; }
    );
}
Run Code Online (Sandbox Code Playgroud)

实例:http://ideone.com/fR5S9H


sud*_*ash 5

int a[3] = {1,2,3};
int size = 3;
int i = 0;

do {
    std::cout << a[i];
} while (++i < size && std::cout << ", ");
Run Code Online (Sandbox Code Playgroud)

输出:

1, 2, 3 
Run Code Online (Sandbox Code Playgroud)

目标是使用&&评估方式.如果第一个条件为真,则计算第二个条件.如果不是,则跳过第二个条件.


归档时间:

查看次数:

6697 次

最近记录:

7 年,11 月 前