【问题标题】:How to iterate over a specific range of std::set/std::multiset?如何迭代特定范围的 std::set/std::multiset?
【发布时间】:2021-06-01 19:01:31
【问题描述】:

对于像 std::set<std::string> set={ "aaa", "bbb", "ccc", "ddd", "eee", "fff" }; 这样的 std::set 如何在不跟踪额外索引的情况下迭代该集合的特定范围 [a, b]?

类似这样的:

for(auto it = set.begin(); it!=set.begin()+b; ++it)
   std::cout << *it << " ";

或者像这样:

for(auto it = set.begin()+a; it!=set.begin()+b; ++it)
   std::cout << *it << " ";

a&lt;=bb&lt;=set.size() 的位置

【问题讨论】:

  • std::set 没有随机访问权限iterator。它有一个双向的iterator。你不能那样做。
  • 你到底要问什么,从集合中以“a”开头的所有元素到以“b”结尾的所有元素的范围(比方说 3-5)?
  • @ThePhilomath 我知道!但是可以在 for 循环中使用额外的索引来完成,但是我想知道是否有更好的方法来做到这一点!

标签: c++ stl associative-array


【解决方案1】:

你不能做set.begin()+a,但你可以做std::advance(it, a)

#include <iostream>
#include <set>
#include <string>
 
int main()
{
    std::set<std::string> set={ "aaa", "bbb", "ccc", "ddd", "eee", "fff" };
    size_t a = 1, b = 4;
    auto it = set.begin(), it_end = set.begin();
    std::advance(it, a);
    std::advance(it_end, b);
    for(; it!= it_end; ++it)
        std::cout << *it << " ";
}

很遗憾,您不能执行auto it = std::advance(set.begin(), a); 之类的操作,因为advance 通过引用获取迭代器并就地更改它。

https://ideone.com/8nRjgr

更好的解决方案使用std::next,感谢@Evg:

#include <iostream>
#include <set>
#include <string>
 
int main()
{
    std::set<std::string> set={ "aaa", "bbb", "ccc", "ddd", "eee", "fff" };
    size_t a = 1, b = 4;
    for(auto it = std::next(set.begin(), a), it_end = std::next(it, b-a); it != it_end; ++it)
        std::cout << *it << " ";
}

https://ideone.com/6wgpMZ

【讨论】:

  • 看看std::next,你可以做std::next(set.begin(), a)
  • 我宁愿不要从开始前进两次,而是auto it = set.begin(); std::advance(it, a); auto it_end = it; std::advance(it, b-a);,或者更紧凑:auto it = std::next(set.begin(), a), it_end = std::next(it, b-a);。您不应该在循环的条件检查中包含对 std::next 的调用,因为它每次都会重新评估并且并不便宜。
  • @BoboFeugo 由于两个迭代器的类型相同,您可以这样做for(auto it = std::next(set.begin(), a), it_end = std::next(it, b-a); it != it_end; ++it)
  • 值得一提的是,set 不能像其他(序列)容器那样迭代的原因是它效率低下。原则上,std::set 可以与for(auto it = set.begin(); it!=set.begin()+b; ++it) 一起使用,但这是故意禁用的。你不得不三思而后行
  • @BoboFeugo 这取决于迭代器的类型。对于随机访问迭代器,成本与整数算术相同。对于其他人(比如std::set::iterator!),它是不是!您需要跟踪从一个节点到另一个节点的链接,直到您到达所需的节点。为了比较,看一个链表——原理相同,但更容易理解,因为只有一个后继节点存在。
【解决方案2】:

你可以编写自己的slice 类,有点像std::span

#include <cstdio>
#include <set>
#include <string>

template <class Iter>
class slice {
 private:
  Iter _beg;
  Iter _end;

 public:
  slice(Iter beg, Iter end) noexcept 
    : _beg{beg}
    , _end{end} 
  {}

  slice(Iter beg, std::size_t sz) noexcept 
    : slice(beg, std::next(beg, sz)) 
  {}

  auto begin() const noexcept { return _beg; }
  auto end() const noexcept { return _end; }
};

int main() {
  std::set<std::string> const set{"aaa", "bbb", "ccc", "ddd", "eee", "fff"};

  slice const sl(set.begin(), 3);

  for (auto const& elm : sl) std::puts(elm.c_str());
}

Godbolt

【讨论】:

  • 也很有趣,谢谢!在定义slice const sl(set.begin(), 3) 时,您似乎缺少模板参数。还是这是 c++20 的功能?
  • @BoboFeugo 这适用于 C++17 及更高版本。它从构造函数参数推断类型。 gcc.godbolt.org/z/jvxG5n
猜你喜欢
  • 2015-08-01
  • 2012-10-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-04-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多