【问题标题】:Flattening iterator展平迭代器
【发布时间】:2023-03-27 02:59:01
【问题描述】:

是否有任何现有的迭代器实现(可能在 boost 中)实现某种扁平化迭代器?

例如:

unordered_set<vector<int> > s;

s.insert(vector<int>());
s.insert({1,2,3,4,5});
s.insert({6,7,8});
s.insert({9,10,11,12});

flattening_iterator<unordered_set<vector<int> >::iterator> it( ... ), end( ... );
for(; it != end; ++it)
{
    cout << *it << endl;
}
//would print the numbers 1 through 12

【问题讨论】:

  • 它会打印数字 1 到 12,但不一定按顺序打印,因为您在示例中使用了 unordered 集,对吧?
  • @James:是的,在这个例子中我不关心它们的打印顺序。

标签: c++ c++11 iterator std


【解决方案1】:

除了Matthieu的回答,你可以自动统计iterable/container的维度数量。但首先我们必须在某些东西是可迭代/容器时设置规则:

template<class T, class R = void>
struct AliasWrapper {
    using Type = R;
};

template<class T, class Enable = void>
struct HasValueType : std::false_type {};

template<class T>
struct HasValueType<T, typename AliasWrapper<typename T::value_type>::Type> : std::true_type {};

template<class T, class Enable = void>
struct HasConstIterator : std::false_type {};

template<class T>
struct HasConstIterator<T, typename AliasWrapper<typename T::const_iterator>::Type> : std::true_type {};

template<class T, class Enable = void>
struct HasIterator : std::false_type {};

template<class T>
struct HasIterator<T, typename AliasWrapper<typename T::iterator>::Type> : std::true_type {};

template<class T>
struct IsIterable {
    static constexpr bool value = HasValueType<T>::value && HasConstIterator<T>::value && HasIterator<T>::value;
};

我们可以这样计算维度:

template<class T, bool IsCont>
struct CountDimsHelper;

template<class T>
struct CountDimsHelper<T, true> {
    using Inner = typename std::decay_t<T>::value_type;
    static constexpr int value = 1 + CountDimsHelper<Inner, IsIterable<Inner>::value>::value;
};

template<class T>
struct CountDimsHelper<T, false> {
    static constexpr int value = 0;
};

template<class T>
struct CountDims {
    using Decayed = std::decay_t<T>;
    static constexpr int value = CountDimsHelper<Decayed, IsIterable<Decayed>::value>::value;
};

然后我们可以创建一个包含begin()end() 函数的视图包装器。

template<class Iterable, int Dims>
class Flatten {
public:
    using iterator = FlattenIterator<Iterable, Dims>;

private:
    iterator _begin{};
    iterator _end{};

public:
    Flatten() = default;

    template<class I>
    explicit Flatten(I&& iterable) :
        _begin(iterable),
        _end(iterable)
    {}

    iterator begin() const {
        return _begin;
    }

    iterator end() const {
        return _end;
    }
};

为了使对象Flatten的创建更容易一些,我们定义了一个辅助函数:

template<class Iterable>
Flatten<std::decay_t<Iterable>, CountDims<Iterable>::value - 1> flatten(Iterable&& iterable) {
    return Flatten<std::decay_t<Iterable>, CountDims<Iterable>::value - 1>(iterable);
}

用法:

std::vector<std::vector<int>> vecs = {{1,2,3}, {}, {4,5,6}};

for (int i : flatten(vecs)) {
    // do something with i
}

【讨论】:

    【解决方案2】:

    我到的有点晚,但我刚刚发布了a library (multidim) 来处理这个问题。用法很简单:用你的例子,

    #include "multidim.hpp"
    
    // ... create "s" as in your example ...
    
    auto view = multidim::makeFlatView(s);
    // view offers now a flattened view on s
    
    // You can now use iterators...
    for (auto it = begin(view); it != end(view); ++it) cout << *it << endl;
    
    // or a simple range-for loop
    for (auto value : view) cout << value;
    

    该库仅是标头,没有依赖项。不过需要 C++11。

    【讨论】:

      【解决方案3】:

      我决定对扁平化迭代器概念进行一些“改进”,尽管正如 James 所指出的,你被困在使用 Ranges (最里面的容器除外),所以我只是使用了范围,从而获得了一个 扁平化范围,具有任意深度。

      首先我使用了积木:

      template <typename C>
      struct iterator { using type = typename C::iterator; };
      
      template <typename C>
      struct iterator<C const> { using type = typename C::const_iterator; };
      

      然后定义了一个(非常小的)ForwardRange 概念:

      template <typename C>
      class ForwardRange {
          using Iter = typename iterator<C>::type;
      public:
          using pointer = typename std::iterator_traits<Iter>::pointer;
          using reference = typename std::iterator_traits<Iter>::reference;
          using value_type = typename std::iterator_traits<Iter>::value_type;
      
          ForwardRange(): _begin(), _end() {}
      
          explicit ForwardRange(C& c): _begin(begin(c)), _end(end(c)) {}
      
          // Observers
          explicit operator bool() const { return _begin != _end; }
      
          reference operator*() const { assert(*this); return *_begin; }
          pointer operator->() const { assert(*this); return &*_begin; }
      
          // Modifiers
          ForwardRange& operator++() { assert(*this); ++_begin; return *this; }
          ForwardRange operator++(int) { ForwardRange tmp(*this); ++*this; return tmp; }
      
      private:
          Iter _begin;
          Iter _end;
      }; // class ForwardRange
      

      这是我们在这里的积木,尽管事实上我们可以只用剩下的部分:

      template <typename C, size_t N>
      class FlattenedForwardRange {
          using Iter = typename iterator<C>::type;
          using Inner = FlattenedForwardRange<typename std::iterator_traits<Iter>::value_type, N-1>;
      public:
          using pointer = typename Inner::pointer;
          using reference = typename Inner::reference;
          using value_type = typename Inner::value_type;
      
          FlattenedForwardRange(): _outer(), _inner() {}
      
          explicit FlattenedForwardRange(C& outer): _outer(outer), _inner() {
              if (not _outer) { return; }
              _inner = Inner{*_outer};
              this->advance();
          }
      
          // Observers
          explicit operator bool() const { return static_cast<bool>(_outer); }
      
          reference operator*() const { assert(*this); return *_inner; }
          pointer operator->() const { assert(*this); return _inner.operator->(); }
      
          // Modifiers
          FlattenedForwardRange& operator++() { ++_inner; this->advance(); return *this; }
          FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; }
      
      private:
          void advance() {
              if (_inner) { return; }
      
              for (++_outer; _outer; ++_outer) {
                  _inner = Inner{*_outer};
                  if (_inner) { return; }
              }
              _inner = Inner{};
          }
      
          ForwardRange<C> _outer;
          Inner _inner;
      }; // class FlattenedForwardRange
      
      template <typename C>
      class FlattenedForwardRange<C, 0> {
          using Iter = typename iterator<C>::type;
      public:
          using pointer = typename std::iterator_traits<Iter>::pointer;
          using reference = typename std::iterator_traits<Iter>::reference;
          using value_type = typename std::iterator_traits<Iter>::value_type;
      
          FlattenedForwardRange(): _range() {}
      
          explicit FlattenedForwardRange(C& c): _range(c) {}
      
          // Observers
          explicit operator bool() const { return static_cast<bool>(_range); }
      
          reference operator*() const { return *_range; }
          pointer operator->() const { return _range.operator->(); }
      
          // Modifiers
          FlattenedForwardRange& operator++() { ++_range; return *this; }
          FlattenedForwardRange operator++(int) { FlattenedForwardRange tmp(*this); ++*this; return tmp; }
      
      private:
          ForwardRange<C> _range;
      }; // class FlattenedForwardRange
      

      显然,it works

      【讨论】:

      • 小问题:我发现迭代器的名称 Range 有点令人困惑。
      • @Nobody:嗯,这可能是因为它实际上是一个范围而不是真正的迭代器(尽管它可以用作一个)。它将要迭代的范围的两个“末端”嵌入到一个对象中,使其自给自足。确实很不幸,但是许多有趣的范围不能轻易地表示为迭代器对(或者至少,不是没有冗余)。
      • 你是从看到存储对象的实现者的角度来争论的。我从类似于迭代器的界面争论。我希望一个范围可以插入for(auto elem: range)。无论如何,这只是命名事物。还是不错的。
      • @Nobody:实际上,我是从两个 POV 中争论的,如果你看一下 ideone 示例,它不会被用作迭代器......并且不会像新的@ 987654327@循环。
      【解决方案4】:

      我不知道主要库中的任何实现,但它看起来是一个有趣的问题,所以我编写了一个基本实现。我只用我这里提供的测试用例进行了测试,所以我不建议在没有进一步测试的情况下使用它。

      问题比看起来要复杂一些,因为某些“内部”容器可能是空的,您必须跳过它们。这意味着将flattening_iterator 推进一个位置实际上可能将迭代器推进到“外部”容器中不止一个位置。因此,flattening_iterator 需要知道外部范围的终点在哪里,以便知道何时需要停止。

      这个实现是一个前向迭代器。双向迭代器还需要跟踪外部范围的开始。 flatten 函数模板用于简化flattening_iterators 的构建。

      #include <iterator>
      
      // A forward iterator that "flattens" a container of containers.  For example,
      // a vector<vector<int>> containing { { 1, 2, 3 }, { 4, 5, 6 } } is iterated as
      // a single range, { 1, 2, 3, 4, 5, 6 }.
      template <typename OuterIterator>
      class flattening_iterator
      {
      public:
      
          typedef OuterIterator                                outer_iterator;
          typedef typename OuterIterator::value_type::iterator inner_iterator;
      
          typedef std::forward_iterator_tag                iterator_category;
          typedef typename inner_iterator::value_type      value_type;
          typedef typename inner_iterator::difference_type difference_type;
          typedef typename inner_iterator::pointer         pointer;
          typedef typename inner_iterator::reference       reference;
      
          flattening_iterator() { }
          flattening_iterator(outer_iterator it) : outer_it_(it), outer_end_(it) { }
          flattening_iterator(outer_iterator it, outer_iterator end) 
              : outer_it_(it), 
                outer_end_(end)
          { 
              if (outer_it_ == outer_end_) { return; }
      
              inner_it_ = outer_it_->begin();
              advance_past_empty_inner_containers();
          }
      
          reference operator*()  const { return *inner_it_;  }
          pointer   operator->() const { return &*inner_it_; }
      
          flattening_iterator& operator++()
          {
              ++inner_it_;
              if (inner_it_ == outer_it_->end())
                  advance_past_empty_inner_containers();
              return *this;
          }
      
          flattening_iterator operator++(int)
          {
              flattening_iterator it(*this);
              ++*this;
              return it;
          }
      
          friend bool operator==(const flattening_iterator& a, 
                                 const flattening_iterator& b)
          {
              if (a.outer_it_ != b.outer_it_)
                  return false;
      
              if (a.outer_it_ != a.outer_end_ && 
                  b.outer_it_ != b.outer_end_ &&
                  a.inner_it_ != b.inner_it_)
                  return false;
      
              return true;
          }
      
          friend bool operator!=(const flattening_iterator& a,
                                 const flattening_iterator& b)
          {
              return !(a == b);
          }
      
      private:
      
          void advance_past_empty_inner_containers()
          {
              while (outer_it_ != outer_end_ && inner_it_ == outer_it_->end())
              {
                  ++outer_it_;
                  if (outer_it_ != outer_end_) 
                      inner_it_ = outer_it_->begin();
              }
          }
      
          outer_iterator outer_it_;
          outer_iterator outer_end_;
          inner_iterator inner_it_;
      };
      
      template <typename Iterator>
      flattening_iterator<Iterator> flatten(Iterator it)
      {
          return flattening_iterator<Iterator>(it, it);
      }
      
      template <typename Iterator>
      flattening_iterator<Iterator> flatten(Iterator first, Iterator last)
      {
          return flattening_iterator<Iterator>(first, last);
      }
      

      以下是一个最小的测试存根:

      #include <algorithm>
      #include <iostream>
      #include <set>
      #include <vector>
      
      int main()
      {
          // Generate some test data:  it looks like this:
          // { { 0, 1, 2, 3 }, { 4, 5, 6, 7 }, { 8, 9, 10, 11 } }
          std::vector<std::vector<int>> v(3);
          int i(0);
          for (auto it(v.begin()); it != v.end(); ++it)
          {
              it->push_back(i++); it->push_back(i++);
              it->push_back(i++); it->push_back(i++);
          }
      
          // Flatten the data and print all the elements:
          for (auto it(flatten(v.begin(), v.end())); it != v.end(); ++it)
          {
              std::cout << *it << ", ";
          }
          std::cout << "\n";
      
          // Or, since the standard library algorithms are awesome:
          std::copy(flatten(v.begin(), v.end()), flatten(v.end()), 
                    std::ostream_iterator<int>(std::cout, ", "));
      }
      

      就像我一开始说的,我还没有彻底测试过。如果您发现任何错误,请告诉我,我很乐意纠正它们。

      【讨论】:

      • 非常感谢您花时间写这篇文章。我还没有做太多测试,但我遇到的唯一问题是 gcc 抱怨“typedef typename OuterIterator”说它应该是“typedef OuterIterator”。
      • @George:啊,谢谢。这是一个复制和粘贴错误,加上 Visual C++ 中不严格的标准合规性:-P。
      • @George:提醒:operator== 重载中存在一个错误,导致它仅在与结束迭代器比较时才起作用;我在编辑中更正了它。
      • 感谢您告诉我。另外,您能否看看我上面的双向实现,如果您看到更好的实现方式,请告诉我。谢谢。
      • @James:有一天我希望你能四处走走并发布你的迭代器库:)
      【解决方案5】:

      你可以在 boost 中使用迭代器外观。

      我编写了迭代器产品,您可以将其用作模板: http://code.google.com/p/asadchev/source/browse/trunk/work/cxx/iterator/product.hpp

      【讨论】:

      • 链接已损坏...好吧,它通向某个地方,但我没有找到迭代器
      猜你喜欢
      • 2014-03-25
      • 1970-01-01
      • 1970-01-01
      • 2023-02-13
      • 1970-01-01
      • 2018-01-09
      • 2012-03-08
      • 2019-12-25
      • 2010-09-12
      相关资源
      最近更新 更多