简体   繁体   中英

In armadillo, how to interpret result of find on submatrix

Consider the following code using Armadillo to find the first 7 (searching down columns first) in the specified rows and columns of a matrix:

using namespace arma;

uvec2 firstEqual7(const mat& X, const uvec& rows, const uvec& cols)
{
    const uword findCount = 1;
    uvec results = find(X.submat(rows, cols) == 7, findCount);
    if (results.empty()) throw std::logic_error("no 7 found!");
    return ind2sub(X, results[0]);
}

There are two problems with this. First, find gives us a single vectorised index for each result, so to convert this to a row and column we have to call a function somewhat like MATLAB's ind2sub , but surprisingly none exists in Armadillo. OK, that's a pain, but I could write one myself in two lines. (But I mention it in case someone knows a better way for this.)

A bigger problem is that the result is an index into the submatrix eg if X is 100x100 but rows=cols={98,99} then the bottom right hand corner is indicated by find(...)==3 rather than find(...)==9999 . How could I get the absolute result?

One option is this:

using namespace arma;

uvec2 firstEqual7(const mat& X, const uvec& rows, const uvec& cols)
{
    const uword findCount = 1;
    auto submatOfX = X.submat(rows, cols);  // DO NOT DO THIS!
    uvec results = find(submatOfX == 7, findCount);
    if (results.empty()) throw std::logic_error("no 7 found!");
    uvec2 submatIndices = ind2sub(submatOfX, results[0]);
    return uvec2{rows[submatIndices[0]], cols[submatIndices[1]]}; 
}

I think this will run (assuming we've implemented ind2sub ), but is wildly inefficient. The problem involves the fact that that submatOfX is not really a matrix but a little proxy type, so that Armadillo doesn't need to copy all the submatrix elements into a new matrix just to perform the find operation. But passing it to ind2sub implicitly converts it to a mat , which makes the matrix copy take place after all!

Pushing on with this possible solution, I could make ind2sub templated on the matrix-like type so that the proxy is passed in directly. This would probably work but seems rather complicated solution to what I had originally hoped would be a very short function. What's more, the proxy types in Armadillo aren't really documented and I don't think that users are meant to mess with them, so I'm worried that keeping one around in a variable, even briefly, is a bit fragile. Does anyone have any better ideas?

In the absence of any answers, I think the conclusion has to be that Armadillo's MATLAB-like syntax just isn't powerful enough for tasks like this. It's nice for little things that fit into one line, but if it's more complicated then we're forced to spell out the iteration ourselves. Arguably the following code is at least as clear as the first code snippet anyway, despite being slightly longer.

using namespace arma;

// NOTE: rows and cols are now logical vectors (i.e. {1 0 1 1 0 ...}),
// rather than vectors of indices (i.e. {0, 2, 3, ...})
uvec2 firstEqual7(const mat& X, const uvec& rows, const uvec& cols)
{
    for (uword c = 0; c < X.n_cols; ++c) {
        if (!cols(c)) continue;
        for (uword r = 0; r < X.n_rows; ++r) {
            if (!rows(r)) continue;
            if (X(r, c) == 7) {
                return uvec2{r, c};
            }
        }
    }
    throw std::logic_error("no 7 found!");
}

Note that it's important that the outer loop be columns and the inner one be rows. Matrices are stored column-wise by Armadillo, so iterating this way progresses through memory rather than jumping about, which speeds things up because we get more cache hits.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM