- Add nzvals() methods for COO_SparseArray and SVT_SparseArray objects.
  Uncomment nzvals() examples in vignette and SparseArray-class.Rd

- Add unit tests for nzwhich() and nzvals() methods for COO_SparseArray
  and SVT_SparseArray objects.

- To support Shila's use case (see #tech-advisory-board channel on
  community Slack):

  1. We can't use base::apply() on an SparseArray derivative because it
     will turn it into an ordinary (i.e. dense) array.
     Also, trying to circumvent the base::apply() problem by implementing
     our own loop in R e.g. with something like:

       summarize_along_3rd_dim <- function(X, FUN, ...)
       {
           stopifnot(length(dim(X)) == 3L)
           FUN <- match.fun(FUN)
           ans <- matrix(0, nrow=nrow(X), ncol=ncol(X))
           for (i in seq_len(nrow(ans))) {
               for (j in seq_len(ncol(ans))) {
                   ans[i, j] <- FUN(X[i, j, ], ...)
               }
           }
           ans
       }

       res1 <- summarize_along_3rd_dim(big_GxNxk_array, mean)

     is very very slow! e.g. it takes about 17 min. for a 500x200x15000
     SVT_SparseArray object with a 10% density (memory footprint of the
     object is 1.6Gb).

     The following is significantly faster (5.5 sec. instead of 17 min.)
     but feels a little bit too tricky:

       tmp <- lapply(seq_len(ncol(big_GxNxk_array)),
                     function(j) rowMeans(big_GxNxk_array[,j,]));
       res2 <- do.call(cbind, tmp)  # identical to 'res1'

     So we need the following:
       a. We should be able to call col/rowSums() and col/rowMeans(), as well
          as any other matrixStats function on an SVT_SparseArray object with
          an arbitrary number of dimensions, not just on an SVT_SparseMatrix
          object.
       b. The col/rowSums() and col/rowMeans() methods for SVT_SparseArray
          objects should support the 'dims' argument supported by the base
          functions. This would allow us to do

            rowMeans(big_GxNxk_array, dims=2)

          instead of the above trick. This syntax is the natural thing to
          use to compute stats along arbitrary dimensions. Note that its
          implementation could actually use something like the above trick
          but now it would be hidden from the user.
       c. Furthermore, if we find a way to construct 'big_kxGxN_array'
          efficiently (maybe with 'aperm(big_GxNxk_array, perm=c(3,1,2))'),
          then we would be able to simply do 'colMeans(big_kxGxN_array)'
          instead of 'rowMeans(big_GxNxk_array, dims=2)'. There's chance
          that the former will be slightly more efficient than the latter
          (slicing of the SVT will require much less rearrangements,
          no need to transpose each slice, direct summarization of
          the individual leaf vectors of a given slice).
       d. These methods must loop at the C level.
       e. Finally these capabilities should be extended to all matrixStats
          functions.

  2. Extend the capabilities of the SVT_SparseArray() and SparseArray()
     constructors so that they can be used to create a SVT_SparseArray object
     with the specified dimensions and only zeros (i.e. no nonzero data).

  3. Subassignments like this need to work:
       svt[ , , 1] <- svt[ , , 3, drop=FALSE]
       svt[ , , 1] <- svt[ , , 3]

- More SBT ("Sparse Buffer Tree") use cases:

  1. Implement C helper _push_vals_to_SBT_by_Mindex(), and modify coercion
     from COO_SparseArray to SVT_SparseArray to use that instead of
     C_subassign_SVT_by_Mindex(). This will probably lead to a cleaner/simpler
     implementation. But is it faster too?

  2. Revisit implementation of C_subassign_SVT_by_Mindex() and
     C_subassign_SVT_by_Lindex(): Can they use an SBT instead of the "extended
     leaves" approach? E.g. they would use _push_vals_to_SBT_by_Mindex()
     and _push_vals_to_SBT_by_Lindex(), respectively, then "merge" the SBT
     with the original SVT. This will probably lead to a cleaner/simpler
     implementation. But is it faster too?

  3. Revisit implementation of C_readSparseCSV_as_SVT_SparseMatrix(): Try to
     use an SBT instead of an ExtendableJaggedArray. Performance should not
     be impacted. Then we can get rid of the ExtendableJaggedArray thing.

- Improve readSparseCSV() functionality by adding a few read.table-like args
  to it. See https://github.com/Bioconductor/SparseArray/issues/5 for the
  details.

- Rename sparsity -> sparseness?

- To help implement the Kronecker product (see below): Introduce
  arep_times(x, times) and arep_each(x, each) generics. The 'times'
  and 'each' args must be integer vectors with the same length as 'dim(x)'.
  They are multidimensional versions of 'rep(, times=t)' and 'rep(, each=e)'
  that perform the replications along each dimension of the array.

  For example, with x = matrix(1:6, ncol=2):

      > x
           [,1] [,2]
      [1,]    1    4
      [2,]    2    5
      [3,]    3    6

   1. arep_times(x, times=c(2, 4)) returns:

      > do.call(cbind, rep(list(do.call(rbind, rep(list(x), 2))), 4))
           [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
      [1,]    1    4    1    4    1    4    1    4
      [2,]    2    5    2    5    2    5    2    5
      [3,]    3    6    3    6    3    6    3    6
      [4,]    1    4    1    4    1    4    1    4
      [5,]    2    5    2    5    2    5    2    5
      [6,]    3    6    3    6    3    6    3    6

   2. arep_each(x, each=c(2, 4)) returns:

     > matrix(rep(t(matrix(rep(x, each=2), ncol=ncol(x))), each=4),
              byrow=TRUE, ncol=ncol(x)*4)
           [,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8]
      [1,]    1    1    1    1    4    4    4    4
      [2,]    1    1    1    1    4    4    4    4
      [3,]    2    2    2    2    5    5    5    5
      [4,]    2    2    2    2    5    5    5    5
      [5,]    3    3    3    3    6    6    6    6
      [6,]    3    3    3    3    6    6    6    6

  Note that the arep_times() and arep_each() generics really belong to
  the S4Arrays package (with the methods for ordinary arrays defined there
  too).

  Then kronecker(X, Y) (see below) can simply be obtained with:

    arep_each(X, each=dim(Y)) * arep_times(Y, times=dim(X))

  And this should be made the kronecker() method for Array objects.

- Implement kronecker() method (Kronecker product) for SVT_SparseArray objects.
  A quick scan of BioC 3.18 software packages reveals that 25+ packages call
  the kronecker() function. Would be interesting to know how many of them need
  to do this on sparse objects?

  Note that one way to go is to simply implement arep_times() and arep_each()
  methods for SVT_SparseArray objects. This will give us kronecker() for
  free (via the kronecker() method for Array objects defined in S4Arrays,
  see above).

  Good things to test (on SVT_SparseArray objects) once this is implemented:

  1. First mixed-product property (mixing with element-wise array
     multiplication a.k.a. "Hadamard product"):
       With 4 arrays A, B, C, D, all with the same number of dimensions,
       with A and C conformable, with B and D conformable, then:
         kronecker(A, B) * kronecker(C, D)
       must be the same as:
         kronecker(A * C, B * D)
       For example:
         A <- array(1:60, dim=5:3)
         C <- array(runif(60), dim=5:3)
         B <- array(101:180, dim=c(2,10,4))
         D <- array(runif(80), dim=c(2,10,4))
         stopifnot(all.equal(kronecker(A, B) * kronecker(C, D),
                             kronecker(A * C, B * D)))
         stopifnot(all.equal(kronecker(B, A) * kronecker(D, C),
                             kronecker(B * D, A * C)))

  2. Second mixed-product property (mixing with matrix multiplication):
       With 4 matrices A, B, C, D, with dimensions that allow one to do
       A %*% C and B %*% D, then:
         kronecker(A, B) %*% kronecker(C, D)
       must be the same as:
         kronecker(A %*% C, B %*% D)
       For example:
         A <- matrix(1:12, ncol=3)
         C <- matrix(runif(18), nrow=3)
         B <- matrix(101:120, ncol=5)
         D <- matrix(runif(10), nrow=5)
         stopifnot(all.equal(kronecker(A, B) %*% kronecker(C, D),
                             kronecker(A %*% C, B %*% D)))
         stopifnot(all.equal(kronecker(B, A) %*% kronecker(D, C),
                             kronecker(B %*% D, A %*% C)))

  See https://en.wikipedia.org/wiki/Kronecker_product for other properties
  to test.

- Define apply() and asplit() methods for SparseArray objects that fail with
  a friendly error message. Otherwise base::apply() or base::asplit() get
  called and they call as.array() on the object internally which is BAD!

- Try to speed up SVT_SparseArray transposition by implementing a one-pass
  approach that uses ExtendableJaggedArray intermediate buffers (offss, valss).
  See src/readSparseCSV.c where this approach is already used.
  Note that this will require that ExtendableJaggedArray structs are able
  to support other types of columns (only support int at the moment).

- Support 'match(svt, table)' where 'svt' is an SVT_SparseArray object
  and 'table' an atomic vector. This will give us 'svt %in% table' for free.

- Implement C_subassign_SVT_with_Rarray() and C_subassign_SVT_with_SVT().

- Speed up row selection: x[row_idx, ]

- Implement more matrixStats methods for SVT_SparseMatrix objects. Those
  that are still missing and are actually used in Bioconductor are:
  rowMeans2, rowSums2, rowRanks, rowQuantiles, rowMads, rowIQRs, rowAlls,
  rowCumsums, rowWeightedMeans, rowAnyNAs) + corresponding col* methods.

- Implement more summarization methods for SVT_SparseArray objects.
  See R/SparseArray-summarization.R

- Implement readMatrixMarket() to read a Matrix Market file as a SparseMatrix
  object, and writeMatrixMarket() to write a SparseMatrix object to a Matrix
  Market file. These will be the analogs of readMM() and writeMM() from the
  Matrix package.
  See https://math.nist.gov/MatrixMarket/formats.html

- Implement table() method for SVT_SparseArray objects of type logical,
  integer, or raw (should it go in R/SparseArray-summarization.R?)

- Add unit tests for the SVT_SparseArray stuff.

- Implement function for loading a TENxMatrixSeed subset as an
  SVT_SparseMatrix. Test it on the 1.3 Million Brain Cell Dataset.

- Go after dgCMatrix objects in ExperimentHub (query(eh, "dgCMatrix")),
  convert them to SVT_SparseMatrix objects and try to do the things that
  are usually done on them.

- Convert 8322787x1098 dgTMatrix (ExperimentHub resource EH5453) to
  SVT_SparseMatrix and try to do the things that the curatedMetagenomicData
  folks usually do on it.

- Do we need a dedicated container for SVT_SparseArray objects of type
  logical with no NAs? There's only one possible nonzero value for these
  objects (TRUE) so there's no need to store the nonzero values. More
  precisely, the dedicated container could use the following simpler internal
  representation: keep the tree structure of SVT_SparseArray objects but
  instead of using a list of 2 parallel vectors for each non-NULL leaf (this
  list stores the offset/value pairs), use an integer vector to only store
  the offsets.
  The dedicated container would be called OVT_SparseArray, for "Offset Vector
  Tree". It would be another SparseArray subclass. Would inherit the "dim"
  and "dimnames" slots from SparseArray and only add the "SVT" slot ("type"
  slot not needed).
  This would make the memory footprint of an OVT_SparseArray object half
  that of an SVT_SparseArray of type logical!
  Notes:
    - The type of an OVT_SparseArray object would always be "logical".
    - 2 typical use cases for OVT_SparseArray: (1) represent the result of
      calling is.na() on an SVT_SparseArray object, (2) represent the
      adjacency matrix of a graph.
    - OVT_SparseArray is to SVT_SparseArray what ngCMatrix is to dgCMatrix.
    - Coercion from SVT_SparseArray to OVT_SparseArray should be supported.
      Would replace all sparse vectors in the tree with offset vectors, by
      stripping off the "vals" component from the non-NULL leaves.
    - Coercion back from OVT_SparseArray to SVT_SparseArray should also be
      supported. Would produce a SVT_SparseArray of type logical.
    - Like with a logical SVT_SparseArray (and, conceptually, with any
      array-like object of type "logical"), we should be able to use an
      OVT_SparseArray object to subset another array-like object of identical
      dimensions (conformable arrays).
  ALTERNATE APPROACHES:
  - Make OVT_SparseArray an extension of SVT_SparseArray.
  - Or simply don't introduce a new class for this and make it an internal
    business of the SVT_SparseArray representation to use either a Sparse
    Vector Tree for the general case or an Offset Vector Tree for the
    special "type=logical with no NAs" case.
