获取密集 Eigen::Matrix 对象的所有非零值

Get all non-zero values of a dense Eigen::Matrix object

假设您有一个动态大小的 Eigen::Matrix 对象并且只想对非零值进行一些计算,您如何获得所有非零值的向量或列表表示形式?

Matrix3f m;
m << 1, 0, 0,
     0, 5, 6,
     0, 0, 9;
VectorXf v = get_non_zero_values(m);
cout << v;

应该给你

1 5 6 9

如何使用 Eigen 完成此操作(最有效)?

在网络上进行了大量研究并受此启发后 Whosebug post 我想出了自己的解决方案

template <typename T>
Eigen::Matrix<T, Eigen::Dynamic, 1> get_non_zeros(Eigen::Matrix<T, Eigen::Dynamic, Eigen::Dynamic>& _input)
{
    Eigen::Matrix<T, Eigen::Dynamic, 1> reduced(Eigen::Map<Eigen::Matrix<T, Eigen::Dynamic, 1>>(_input.data(), _input.size()));
    Eigen::Matrix<bool, Eigen::Dynamic, 1> empty = (reduced.array() == 0).rowwise().all();

    size_t last = reduced.rows() - 1;
    for ( size_t i = 0; i < last + 1;) {
        if ( empty(i) ) {
            reduced.row(i).swap(reduced.row(last));
            empty.segment<1>(i).swap(empty.segment<1>(last));
            --last;
        }
        else {
            ++i;
        }
    }

    reduced.conservativeResize(last + 1, reduced.cols());

    return reduced;
}

使用临时 std::vector 可以应用 push_back 方法来存储非零值。 std::vector 的内容然后可以 Mapped 到 Eigen::Vector

VectorXf get_non_zero_values(const MatrixXf& m) {
  std::vector<float> nzv;
  for (int i = 0; i < m.size(); ++i) {
    if (m(i)) nzv.push_back(m(i));    
  }
Map<VectorXf> nzm(nzv.data(), nzv.size());
return nzm; 
}

这是一个使用 Eigen ver 3.4.0 的解决方案。

如果需要额外的并行性,您可以 #include <execution> 用于 STL 算法。

#include <Eigen/Dense>

#include <algorithm>
#include <functional>
#include <iostream>

int main()
{
    Eigen::MatrixXf const m{{1, 0, 0},
                            {0, 5, 6},
                            {0, 0, 9}};

    auto const size = m.size();
    // create 1D view
    auto const view = m.reshaped().transpose();
    // create boolean markers for nonzeros
    auto const mask = view.array() != 0;
    // create index list and set useless elements to sentinel value
    auto constexpr sentinel = std::numeric_limits<int>::lowest();
    auto idxs = mask.select(Eigen::RowVectorXi::LinSpaced(size, 0, size), sentinel).eval();
    // sort to remove sentinel values
    std::partial_sort(idxs.begin(), idxs.begin() + size, idxs.end(), std::greater{});
    idxs.conservativeResize(mask.count());
    auto const nonzeros = view(idxs.reverse()).eval();
    std::cout << nonzeros << std::endl;
}

输出:

1 5 6 9

解决此问题的另一种可能性是 Eigen's Sparse Module:

Eigen::Matrix3f m;
m << 1, 0, 0, 
     0, 5, 6,
     0, 0, 9;

// Construct a sparse matrix
Eigen::SparseMatrix<float> sparseM(m.sparseView());

你现在可以用sparseM做任何你想做的事了:

// Print non-zeros
for (int i = 0; i < sparseM.nonZeros(); ++i)
  std::cout << *(sparseM.valuePtr() + i) << "\n";


// Construct a dense map
Eigen::Map<Eigen::VectorXf> denseMap(sparseM.valuePtr(), sparseM.nonZeros());

// Construct a dense vector
Eigen::VectorXf denseVector(denseMap);