如何检测在容器上迭代的第一个或最后一个元素?

问题描述 投票:0回答:8

如何以更时尚/简短的方式执行以下操作?

for(i=container.begin(); i!=container.end(); ++i) {
    if (i!=container.begin()) {
        cout << ", ";
    }
    cout << *i;
    j=i;
    if (++j==container.end()) {
        cout << "!" << endl; 
    }
}

foreach 这样的解决方案是可以接受的(不过,第一个和last元素上的操作需要是可配置的)。

附注 有许多答案正在处理第一个元素,但不是最后一个元素。这就是我所说的 handling 最后一个元素的意思:

for(i=container.begin(); i!=container.end(); ++i) {
    j=i;
    if (i==container.begin()) {
        cout << "[" << *i << "]" << endl;
    } else if (++j==container.end()) {
        cout << ", (" << *i << ")" << "!" << endl; 
    } else {
         cout << ", " << *i;
    }
}

你不觉得处理循环体外的第一个元素很容易吗?真正的问题是最后一个!我很抱歉无法澄清提出问题的要点。我想我最终会接受排名最高的答案。

c++ stl
8个回答
3
投票

Boost 有 next/prior 有时可以在这种情况下提供帮助。

for(i=container.begin(); i!=container.end(); ++i) {
    if (boost::next(i) == container.end()) {
         std::cout << "!" << std::endl;
    }
}

尽管对于这种特定情况,我只是输出第一个元素,从第二个元素循环到最后一个元素,同时始终输出“,”,然后输出“!”循环结束后。 (正如其他人已经建议的那样)

我不认为将特殊情况移动到循环内,然后在循环内检查它们有什么意义......


2
投票

我的建议是:在这个循环中检测任何东西是没有意义的!

由于特殊情况位于容器的开头和结尾,因此很容易从循环内删除它们的处理。

以下函数将打印任何其元素可以被

<<
'ed 为
std::ostream
的容器类的内容:

template < class Container >
void print(Container const & container)
{
    typename Container::const_iterator current = container.begin();
    typename Container::const_iterator const end = container.end();
    if (current != end)
    {
        std::cout << *current;
        for (++current; current != end; ++current)
        { 
            std::cout << ", " << *current;
        }
        std::cout << "!" << std::endl;
    }
}

1
投票

在你的代码中,

if (i==container.end()) {
    cout << "!" << endl; 
}

永远不会发生。

我自己的方法是使用容器大小(我认为 size() 现在对于所有标准库容器来说都是恒定时间)。在循环中维护一个计数,显然,当 count == size() - 1 时,您处于结束状态;而当 count == 0 时,您处于循环开始状态。


1
投票

稍微移动

++i

i = container.begin();
while(i != container.end()) {
    if (i != container.begin()) {
        cout << ", ";
    }
    cout << *i;
    if (++i == container.end()) {
        cout << "!" << endl; 
    }
}

1
投票

由于

container
不是你定义的,我用了最简单的 -
vector

template <class T>
string vector_join( const vector<T>& v, const string& token ){
  ostringstream result;
  for (typename vector<T>::const_iterator i = v.begin(); i != v.end(); i++){
    if (i != v.begin()) result << token;
    result << *i;
  }
  return result.str();
}

//usage
cout << vector_join( container, ", " ) << "!";

0
投票
template < class TContainerType>
void print(TContainerType const & i_container)
  {
  typename TContainerTypeconst ::const_iterator current = i_container.begin();
  typename TContainerTypeconst ::const_iterator const end = i_container.end();
  if(current != end)
    {
    std::cout << *current++;
    while(current != end)
      std::cout << ", " << *current++;
     }
  std::cout << "!" << std::endl;
  }

0
投票

将第二部分从循环中取出。

for(i=container.begin(); i!=container.end(); ++i) {
    if (i != container.begin()) {
        cout << ", ";
    }
    cout << *i;
}
cout << "!" << endl; 

0
投票

我也经常遇到最后一个元素的问题。

还经常存在如何知道某个元素是否不是最后一个元素的问题。

我也最喜欢 @Pieter 的变体,因为它确实使用方法

next
捕获了最后一个元素,自 C++17 以来,该方法也可在
stl
中使用。

这是一些对其他人也可能有用的替代方案。

不使用下一个?

下面的代码会检测

i
是否“指向”最后一个元素而不使用 next,这具有一些运行时优势,特别是如果我们有一个需要更多向前迭代成本的容器。

为了摆脱使用

next
,以下代码实现了这一点:

auto    iBegin = container.begin();
auto    iEnd = container.end();
for ( auto i = iBegin, iNext = iBegin; i != iEnd; i = iNext ) {
    iNext++;
    cout << *i;
    if ( iNext == iEnd ) {
        cout <<  "!" << endl;
    } else {
        cout <<  ", ";
    }
}

但是,我赞成创建一个适合所有四种情况的通用解决方案:

  • 仅最后一个元素并且
  • 仅第一个元素并且
  • 第一个和最后一个元素(发生在容器中的 2 个元素)
  • 既不是第一个也不是最后一个元素

通用解决方案

我尝试创建一个

for_each
版本,可以使用 lambda / 闭包来处理这个问题。

如果我们可以像这样迭代会怎么样:

forEachConst( container, []( auto itVector, auto pos ){ 
        cout << (*itVector);
        if ( ! ( pos.isLast ) ) { 
            cout <<  ", ";
        } else {
            cout <<  "!" << endl;
        }
    }
);

这可以通过以下模板函数来实现:

class ForEachPos {
public:
    bool    isFirst;
    bool    isLast;
};

template< typename TIteratingContainer >
void forEachConst( const TIteratingContainer& iteratingContainer, std::function< void( typename TIteratingContainer::const_iterator&, const ForEachPos& ) > closure ) {
    typedef typename TIteratingContainer::const_iterator TIterator;
    ForEachPos    positionClass = ForEachPos{ true, false };
    TIterator               itBegin = iteratingContainer.begin();
    TIterator               itEnd = iteratingContainer.end();
    TIterator               itNext = itBegin;
    for ( TIterator it = itBegin; it != itEnd; it = itNext ) {
        itNext++;
        positionClass.isLast = ( itNext == iteratingContainer.end() );
        closure( it, positionClass );
        positionClass.isFirst = false;
    }
}

非常量版本甚至可以修改最后一个元素或最后一个和第一个元素的组合。

template< typename TIteratingContainer >
void forEach( TIteratingContainer& iteratingContainer, std::function< void( typename TIteratingContainer::iterator&, const ForEachPos& ) > closure ) {
    typedef typename TIteratingContainer::iterator TIterator;
    ForEachPos    positionClass = ForEachPos{ true, false };
    TIterator               itBegin = iteratingContainer.begin();
    TIterator               itEnd = iteratingContainer.end();
    TIterator               itNext = itBegin;
    for ( TIterator it = itBegin; it != itEnd; it = itNext ) {
        itNext++;
        positionClass.isLast = ( itNext == iteratingContainer.end() );
        closure( it, positionClass );
        positionClass.isFirst = false;
    }
}

已测试

我使用空向量以及具有一、二和三个元素的向量测试了代码。

兼容性

该代码至少适用于

-std=c++20

最后的话

短/Sylish?

不知道这样算不算时尚。至少对于定义行为的身体部位来说,它非常具有表现力和简短,例如:

cout << (*itVector);
if ( ! ( pos.isLast ) ) { 
    cout <<  ", ";
} else {
    cout <<  "!" << endl;
}

快吗?

该解决方案在每次迭代中不再使用

next
。这可以节省一些 CPU 使用量,特别是如果
next
应用于容器,而
next
的 CPU 使用成本很高。另一方面,它同时也带来了一些更多的评估成本
positionClass.isFirst

© www.soinside.com 2019 - 2024. All rights reserved.