为矩阵设计一个简单的C ++迭代器

时间:2019-06-29 09:36:12

标签: c++ c++11

我当时在考虑使用++操作设计一个简单的C ++迭代器,对于诸如STL之类的向后和向前迭代,行为会有所不同。这样矩阵A可以通过下面的行和列进行访问,

  A.row(3).begin()
  A.row(3).end()
  A.col(3).begin()
  A.col(3).end()
  A.col(3).rbegin()
  A.col(3).rend()

  ++ A.row(3).begin()
  ++ A.col(3).rbegin()

我的矩阵类如下所示,

class Matrix {
 public:
  Iter row(size_t rowID);
  Iter col(size_t colID);
 private:
  vector<int> data_{1, 2, 3, 4, 5, 6};
  size_t nRow{3};
  size_t nCol{2};
};

关于如何设计我的Iter类有什么建议吗?

2 个答案:

答案 0 :(得分:2)

您可以简单地将“步幅”存储在迭代器内部,即每次指针递增或递减时指针移动的距离。沿着一个轴的步幅为1,而在另一个轴上的步幅为矩阵的尺寸。

如果您的矩阵是这样:

1 2 3 4
6 7 8 9

然后row(0).begin()指向1和row(0).end()指向6(跨度为1),而column(2).begin()指向3和column(3 ).end()指向8以下(步幅为4)的未使用单元格。

boost::make_strided_iterator()将为您做到这一点。

答案 1 :(得分:1)

这里没有增强功能的C ++解决方案。我还提供了一个完整且经过测试的源代码示例。

源代码已使用MS Visual Studio 19进行了编译和测试。

第一提示:我将始终使用std::valarray进行矩阵计算。请阅读。

此解决方案的说明:

我们将使用int向量的向量来表示矩阵。 行可以轻松访问。它们是数据矩阵的第一维。如果我们想要一个行的迭代器,我们简单地返回一个向量的标准迭代器。这样,我们将立即具有完整的功能。很简单。

不幸的是,列是不同的。它们是数据连续存储器中的切片。因此,我们将作为解决方案来实现的是:为每个列创建一个向量,并在正确的位置引用数据。

这听起来比以前更容易,因为我们无法在C ++中将引用存储在容器中。因此,可以使用std::reference_wrapper或构建我们自己的参考包装。在将值分配给解引用的std::reference_wrapper并构建自己的值时,我遇到了问题。添加了赋值运算符。

这样,我们可以根据参考向量将迭代器返回到列。

并且,只需简单地重用std :: vector :: iterator功能,我们就可以以最小的工作量为矩阵类提供迭代器的全部功能。

我在主程序中放了一些测试代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
#include <tuple>
#include <sstream>
#include <numeric>


// Unfortunately the std::reference_wrapper does not work as expected.
// So we will build our own one
class IntRef
{
    // Here we will store the reference
    std::tuple<int&> t;
public:
    // Constructor. Take reference and store it in tuple
    IntRef(int&& intV) : t(intV) {}     

    // Assignment to the referenced value
    int operator =(const int i) { std::get<0>(t) = i; return i; }      

    // Explicit type cast to int&
    operator int& () { return std::get<0>(t); }                        

    // And, return the reference
    decltype(&std::get<0>(t)) operator&() { return &std::get<0>(t); }  
};


// Some definitions to make reading easier
using IntRefV = std::vector<IntRef>;
using MatrixCIterator = std::vector<IntRef>::iterator;
using Columns = std::vector<int>;
using MatrixRIterator = Columns::iterator;


// The matrix
class Matrix
{
public:
    // Constructor defines the matrix size
    Matrix(size_t numberOfRows, size_t numberOfColumns);

    // Iterators for rows are simple, becuase we have vectors of columns. Use unterlying iterator
    MatrixRIterator rowIterBegin(size_t row) { return data[row].begin(); }
    MatrixRIterator rowIterEnd(size_t row) { return data[row].end(); }  

    // Column iterator is complicated. Retzurn iterator to vevtor of references to column values
    MatrixCIterator columnIterBegin(size_t column) { return columnReferences[column].begin(); }
    MatrixCIterator columnIterEnd(size_t column) { return columnReferences[column].end(); }

    // Access data of matrix
    std::vector<int>& operator [] (const size_t row) { return data[row]; }

    // And, for debug purposes. Output all data
    friend std::ostream& operator << (std::ostream& os, const Matrix& m) {
        std::for_each(m.data.begin(), m.data.end(), [&os](const Columns& columns) {std::copy(columns.begin(), columns.end(), std::ostream_iterator<int>(os, " ")); std::cout << '\n'; });
        return os;
    }
protected:
    //The matrix, vector of vector of int
    std::vector<Columns> data; 

    // The references to columns in data
    std::vector<IntRefV> columnReferences{};    
};

// Constructor. Build basic matrix and then store references to columns in data 
Matrix::Matrix(size_t numberOfRows, size_t numberOfColumns) : data(numberOfRows, std::vector<int>(numberOfColumns)), columnReferences(numberOfColumns)
{
    for (size_t column = 0; column < numberOfColumns; ++column) 
        for (size_t row = 0; row < numberOfRows; ++row)
            columnReferences[column].emplace_back(IntRef(std::move(data[row][column]))); // Std::move creates a rvalue reference (needed for constructor, nothing will be moved)
}



// Some test data for the istream_iterator
std::istringstream testData("1 2 10");



// Test the matrix
int main()
{
    // Define a matrix with 3 rows and 4 columns
    Matrix matrix(3, 4);
    // Test 1: Fill all values in column 2 with 42
    for (MatrixCIterator ci = matrix.columnIterBegin(2); ci != matrix.columnIterEnd(2); ++ci) {
        *ci = 42;
    }
    std::cout << matrix << "Column 2 filled with 42\n\n";

    // Test 2: Read input from istream and copy put that in column 1
    std::copy_n(std::istream_iterator<int>(testData), 3, matrix.columnIterBegin(1));
    std::cout << matrix << "Column 1 filled with testData '"<< testData.str() << "'\n\n";

    // Test 3: Copy column 2 to cout (Print column 2)
    std::copy(matrix.columnIterBegin(2), matrix.columnIterEnd(2), std::ostream_iterator<int>(std::cout, " "));
    std::cout << "This is column 2\n\n";

    // Test 4: Sum up the first 2 values of column 1 and show result
    std::cout << "\nSum of first 2 values of column 1:  " << std::accumulate(matrix.columnIterBegin(1), matrix.columnIterBegin(1)+2, 0) << "\n\n";

    // Test 5: Fill all values in row 0 with 33
    std::for_each(matrix.rowIterBegin(0), matrix.rowIterEnd(0), [](int& i) { i = 33; });
    std::cout << matrix << "Row 0 filled with 33\n\n";

    return 0;
}

希望这会让您对它的工作方式有所了解。 。