【问题标题】:Most efficient way to populate vector with indices corresponding to columns of a matrix in constructor?用对应于构造函数中矩阵列的索引填充向量的最有效方法?
【发布时间】:2019-04-26 22:19:10
【问题描述】:

我有一个动态的Matrix 模板类,我希望创建一个向量并使用沿列而不是行向下的索引填充它。您可能已经从我刚才所说的内容中猜到了,我将矩阵的实际数据以行主要形式存储在 std::vector<T> 中。这是我的 Matrix 类定义的简化版本,仅包含相关部分。

template<typename T>
class Matrix {
    public:
        Matrix(std::size_t m, std::size_t n, const T &elem = T());
        Matrix(const std::vector<T> &vec, std::size_t m, std::size_t n); 
        Matrix(std::initializer_list<T> list, std::size_t m, std::size_t n);

    private:
        typedef std::vector<std::size_t> reindex_scheme;

        std::vector<T> _data;
        reindex_scheme _colindices;
        std::size_t _m;
        std::size_t _n;

};

基本上,我想要发生的是,当给出这段代码时:

std::vector<int> input {1, 2, 4, 6, 5, 4};
Matrix<int> A(input, 2, 3); // creates a 2x3 matrix with entries 1 2 4
                            //                                   6 5 4

让它按此顺序在_colindices 中存储以下内容:

0 3 1 4 2 5

(如果你明白我在做什么,如果你像这样遍历矩阵,我只是在抓取元素的索引:

 |   /|   /|
 | /  | /  |
\/   \/   \/

希望你能找出箭头)

到目前为止,我为构造函数提供的是“天真的”基于for 的方法:

template<typename T>
Matrix<T>::Matrix(std::size_t m, std::size_t n, const T &elem)
    : _data(m * n, elem),
      _colindices(m * n), 
      _m(m),
      _n(n) {
    for (std::size_t i = 0; i < _n; i++)
        for (std::size_t j = 0; j < _m; j++)
            _colindices[i * _m + j] = j * _n + i;
}

template<typename T>
Matrix<T>::Matrix(const std::vector<T> &vec, std::size_t m, std::size_t n)
    : _data(vec),
      _colindices(m * n), 
      _m(m),
      _n(n) {
    for (std::size_t i = 0; i < _n; i++)
        for (std::size_t j = 0; j < _m; j++)
            _colindices[i * _m + j] = j * _n + i;
}

template<typename T>
Matrix<T>::Matrix(std::initializer_list<T> list, std::size_t m, std::size_t n)
    : _data(list),
      _colindices(m * n), 
      _m(m),
      _n(n) {
    for (std::size_t i = 0; i < _n; i++)
        for (std::size_t j = 0; j < _m; j++)
            _colindices[i * _m + j] = j * _n + i;
}

另外,作为旁注,您可以看到我为三个构造函数所做的愚蠢的代码重复。如果您知道将三合一或至少三合二的最佳方法,也请在此处告诉我。(抱歉,这变成了一个双重问题)

这个for 方法有效,但显然它是O(mn),我喜欢认为有更好、更有效的方法,也许有一些std 方法?我正在考虑使用0 3 制作一个向量然后对其进行迭代,通过将每个元素增加std::transform 或其他内容来组成一个新向量,然后将其添加到最终向量的末尾,直到我到达结束(如果你明白我在说什么)。有点像

Loop Iteration 1:
0 3
Loop Iteration 2:
0 3 1 4
Loop Iteration 3:
0 3 1 4 2 5

你有什么想法?

【问题讨论】:

  • 为什么需要存储这些索引?为什么不只计算某个访问器所需的索引?
  • 除非您的目标是使用调试器检查内容,否则您展示的类没有任何用处。你打算如何访问这些元素?这是决定如何管理数据的关键。如果您打算使用迭代器(您可能应该这样做......),编写一个以行优先顺序访问存储数据的迭代器和另一个以列优先顺序访问存储数据的迭代器是相当简单的。
  • @PeteBecker 我是,我已经省略了 95% 的实际代码。计划使用boost::permutation_iterator 实现列优先迭代,并且由于数据以行优先形式存储,Matrix&lt;T&gt;::row_iterator 实际上只是_data 迭代器的类型定义。
  • permutation_iterator 对于这样一个简单的映射来说听起来有点矫枉过正。只涉及两个数字:n 和 m,它们是已知的。将行重新映射到列的 nxm 矩阵(不管它是如何实现的)具有很多的冗余。
  • 尽管有相反的传言,但迭代器并不难写。他们只是乏味。我经历了一个过程:输入迭代器的要求,输出迭代器的要求,前向迭代器的要求,双向迭代器的要求,随机访问迭代器的要求。每一个都增加了一些东西。

标签: c++ arrays performance vector std


【解决方案1】:

我的第一个想法是你的程序已经完成了很久,我的回答为时已晚。

但是,也许其他 SO 用户也想了解可能的解决方案。

读到你正在使用一个

std::vector 中的行主要形式

建议使用std::valarray。使用std::slice(或其他类型的切片),您可以简单地对您可以想象的所有类型的索引进行操作。索引运算符[] 连同切片将仅在您需要时计算索引。这是非常强大和快速的。

用于构建矩阵的标准 C++ 方法是使用类型为 std::vectorstd::vector。然后访问该数据或对其进行操作。请注意:std::vector&lt;std::vector&lt;int&gt;&gt; 也使用连续内存。您可以以不同的方式访问它。例如:行很容易访问。您可以只使用第一个维度。对于std::vector&lt;std::vector&lt;int&gt;&gt; data;,您只需编写data[row] 即可访问该行中的所有列。

对于列,这更加困难。我的猜测是你想以某种方式访问​​这些列。否则就不需要计算列的索引或按列的顺序。

这样做的缺点是您不能将矩阵与algorithm 库一起使用。如果没有可用的迭代器,嗯,很难。

我可以为您提供一个矩阵的解决方案,该矩阵具有行和列的迭代器。为此,我们使用对std::vector&lt;std::vector&lt;int&gt;&gt; data; 中正确值的引用向量,按列组织。然后我们可以遍历行和列并使用std::algorithms。

为了速度优化,可以在需要时创建列引用。这将降低构造函数的复杂性。

对于您的侧节点:构造函数中的重复代码。请在您的类中创建一个私有函数并在构造函数中调用它。

请查看带有一些测试代码 (MSVS19) 的矩阵骨架:

#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;
}

希望这能提供一个关于它如何工作的想法

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-27
    • 1970-01-01
    • 2014-11-19
    • 2016-06-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-04-24
    相关资源
    最近更新 更多