【问题标题】:Is it possible to iterate through a vector of vectors columnwise?是否可以逐列遍历向量的向量?
【发布时间】:2022-11-02 05:22:01
【问题描述】:

我有一个字符串向量的向量。我想找到每列中最长字符串的长度。所有的子向量都具有相同的长度并存储了一个元素,因此使用两个 for 循环和反向索引很容易找到它。

vector<vector<string>> myvec = {
                                { "a", "aaa",   "aa"},
                                {"bb",   "b", "bbbb"},
                                {"cc",  "cc",  "ccc"}
                               };

但是是否可以在不使用索引的情况下使用迭代器来做到这一点?

【问题讨论】:

  • for (const auto&amp; vec : myvec) { for (const auto&amp; str : vec) { } } ?
  • 为什么要使用迭代器?如果内部向量存储列,您可以轻松使用迭代器
  • 由于每个子向量在技术上可以是不同的长度,因此不确定迭代器在这种情况下通常如何工作。
  • 您绝对可以做的是,为 1d 向量编写一个类,将其作为 2d 向量进行操作。然后编写一个迭代器,它将对col wise或raw wise感兴趣......目前没有其他方法......或者可能是迭代器循环和patellel索引
  • @Borgleader 那将是逐行迭代,而不是逐列迭代。

标签: c++ matrix vector iterator


【解决方案1】:

假设内部向量的长度都相同,你可以有类似的东西

template <typename T>
class columnwise {
    std::vector<std::vector<T>> * underlying;
    struct proxy {
        std::vector<std::vector<T>> * underlying;
        std::vector<T>::difference_type offset;
        struct iterator {
            std::vector<std::vector<T>>::iterator outer;
            std::vector<T>::difference_type offset;
            using reference = typename std::vector<T>::reference;
            using pointer = typename std::vector<T>::pointer;
            iterator operator++() { ++outer; return *this; }
            reference operator*() { return *(outer->begin() + offset); }
            pointer operator->() { return (outer->begin() + offset).operator->(); }
            bool operator==(iterator rhs) { return (outer == rhs.outer) && (offset == rhs.offset); }
        };
    public:
        iterator begin() { return { underlying->begin(), offset }; }
        iterator end() { return { underlying->end(), offset }; }
    };
    struct iterator {
        // member type aliases
        std::vector<std::vector<T>> * underlying;
        std::vector<T>::difference_type offset;
        iterator operator++() { ++offset; return *this; }
        proxy operator*() { return { underlying, offset }; }
        bool operator==(iterator rhs) { return (underlying== rhs.underlying) && (offset == rhs.offset); }
    };
    std::vector<T>::difference_type inner_size() { if (auto it = underlying->begin(); it != underlying->end()) { return it->size(); } return 0; }
public:
    columnwise(std::vector<std::vector<T>> & vec) : underlying(&vec) {}
    iterator begin() { return { underlying, 0 }; }
    iterator end() { return { underlying, inner_size() }; }
};

其中迭代as you expect

【讨论】:

    【解决方案2】:

    这样的事情呢?

    std::vector<std::size_t> max_lengths(myvec.front().size(), 0);
    for (auto const& strings : myvec) {
        std::transform(max_lengths.begin(), max_lengths.end(), strings.begin(), max_lengths.begin(),
            [](std::size_t l, std::string const& s) {
                return std::max(l, s.size());
            }
        );
    }
    

    Demo

    【讨论】:

      【解决方案3】:

      这是一个使用 C++20 范围和 lambda 的解决方案,类似于 Nelfeals 的答案:

      // returns a function returning the i-th element of an iterable container
      auto ith_element = [](size_t i) {
          return [i](auto const& v){
              return v[i];
          };
      };
      
      // returns a range over the i-th column
      auto column = [ith_element](size_t i) {
          return std::views::transform(ith_element(i)); // returns a range containing only the i-th elements of the elements in the input range
      };
      
      // returns the size of something
      auto length = [](auto const& s){ return s.size(); };
      
      // returns the max length of the i-th column
      auto max_length_of_col = [column, length](auto const& v, size_t i) {
          return std::ranges::max(
              v | column(i) | std::views::transform(length)
          );
      };
      

      我个人喜欢范围库如何帮助您通过代码传达意图,而不必规定实现目标的过程。

      请注意,如果您将 ith_element 中的内部 lambda 的主体替换为以下块,它也适用于没有随机访问的可迭代容器。

      auto it = v.cbegin();
      std::ranges::advance(it, i);
      return *it
      

      Demo

      最后,此解决方案可让您遍历一列给定一个指数的列。我建议不要为vector&lt;vector&gt; 实现列迭代器:迭代器的存在意味着内存中存在可以迭代的东西。列的存在仅由您提供给我们的附加信息暗示,即所有行的长度相同。如果你想要列和行的迭代器,我会将你的容器包装成一种新类型(通常称为矩阵或类似名称)正确地传达了这种意图。然后您可以为该新类型实现迭代器,请参阅 Calath 的答案。

      编辑:

      我意识到我反对列迭代器的论点也可以用作反对column 函数的论点。所以here is a solution 让您在基于范围的 for 循环中迭代列,而不是迭代列索引:

      for (auto column : columns(myvec)){
          std::cout << max_length(column) << std::endl;
      }
      

      【讨论】:

      • 我尝试了您编辑的解决方案。它可以编译,但我的 gcc 10.2.1 得到了完全不同的输出,这对我来说毫无意义。
      • 有趣的。 11.2 似乎是最低的 gcc 版本。不知道问题是什么,但事实上,它在较新的版本中工作使它对我来说似乎是一个编译器错误......我的原始答案适用于 gcc 10.2,所以它可能与 ith_element 实现或使用函数而不是 lambdas ......我将不得不进行调查。
      • 这是一个工作版本:godbolt.org/z/jsa4sWqEe。问题似乎来自 ith_element 实现。我仍然不知道 ith_element 实现的确切问题是什么,但至少在这里你有一个工作版本。我会相应地更新答案
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-12
      • 1970-01-01
      • 2023-01-27
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多