SolvedForHome
SolvedForHome

Reputation: 162

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

Asuming you have a dynamic size Eigen::Matrix object and want to do some computation on only non-zero values, how can you get a vector or list representation of all non-zero values?

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

should give you

1 5 6 9

How can this be done with Eigen (most efficiently)?

Upvotes: 0

Views: 1940

Answers (4)

dtell
dtell

Reputation: 2568

Another possibility to solve this issue is a small excursion to 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());

You can now do whatever you want with 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);

Upvotes: 1

Matt Eding
Matt Eding

Reputation: 1002

Here is a solution that uses Eigen ver 3.4.0.

You can #include <execution> for the STL algorithms if desired for extra parallelism.

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

Output:

1 5 6 9

Upvotes: 1

RHertel
RHertel

Reputation: 23788

Using a temporary std::vector makes it possible to apply the push_back method for storing the non-zero values. The content of the std::vector can then be Mapped to an 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; 
}

Upvotes: 0

SolvedForHome
SolvedForHome

Reputation: 162

After a lot of research in the web and inspired by this stackoverflow post I came up with my own solution

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

Upvotes: 1

Related Questions