'How to find the sum of all anti-diagonals?

I have a matrix M:

n = 3    
x=c(0.85, 0.1, 0.05)
M <- matrix(NA, n, n); 

for(i in 1:n){
for(j in 1:n){
M[i,j] = x[i] * x[j]
}}

#       [,1]  [,2]   [,3]
# [1,] 0.7225 0.085 0.0425
# [2,] 0.0850 0.010 0.0050
# [3,] 0.0425 0.005 0.0025

I need to find the sum of all anti-diagonals include M[1,1] and M[n, n]. My attemp is

d <-matrix(c(0, 1, 2, 1, 2, 3, 2, 3, 4), n)
tapply(M, d, sum)

     0      1      2      3      4 
0.7225 0.1700 0.0950 0.0100 0.0025 

The result is correct for me.

Question. How to define the entries of matrix d? May be as function over col(M) and row(M).



Solution 1:[1]

First note that outer can produce the matrix d without explicitly listing its elements.

matrix(c(0, 1, 2, 1, 2, 3, 2, 3, 4), 3)
#>      [,1] [,2] [,3]
#> [1,]    0    1    2
#> [2,]    1    2    3
#> [3,]    2    3    4
outer(0:2, 0:2, `+`)
#>      [,1] [,2] [,3]
#> [1,]    0    1    2
#> [2,]    1    2    3
#> [3,]    2    3    4

Created on 2022-03-24 by the reprex package (v2.0.1)

And use it in a function.

sumAntiDiag <- function(M){
  nr <- nrow(M)
  nc <- ncol(M)
  d <- outer(seq.int(nr), seq.int(nc), `+`)
  tapply(M, d, sum)
}

n <- 3    
x <- c(0.85, 0.1, 0.05)
M <- matrix(NA, n, n); 

for(i in 1:n){
  for(j in 1:n){
    M[i,j] = x[i] * x[j]
  }}

sumAntiDiag(M)
#>      2      3      4      5      6 
#> 0.7225 0.1700 0.0950 0.0100 0.0025

Created on 2022-03-24 by the reprex package (v2.0.1)

Solution 2:[2]

As you mention in your question, row(M) and col(M) can be used, although they start rows/columns at 1 rather than zero, so you need to subtract 2 (1 for each) giving:

tapply(M, row(M) + col(M) - 2, sum)
#     0      1      2      3      4 
#0.7225 0.1700 0.0950 0.0100 0.0025

Solution 3:[3]

You could do:

sapply(seq(3), function(x) seq(3) + x - 2)
#>      [,1] [,2] [,3]
#> [1,]    0    1    2
#> [2,]    1    2    3
#> [3,]    2    3    4

Or more generally,

anti_diagonal <- function(n) sapply(seq(n), function(x) seq(n) + x - 2)

For example:

anti_diagonal(6)
#>      [,1] [,2] [,3] [,4] [,5] [,6]
#> [1,]    0    1    2    3    4    5
#> [2,]    1    2    3    4    5    6
#> [3,]    2    3    4    5    6    7
#> [4,]    3    4    5    6    7    8
#> [5,]    4    5    6    7    8    9
#> [6,]    5    6    7    8    9   10

Solution 4:[4]

Try the code below by defining a function f using embed from base R, i.e.,

f <- function(n) embed(seq(2 * n - 1) - 1, n)[, n:1]

such that

> f(3)
     [,1] [,2] [,3]
[1,]    0    1    2
[2,]    1    2    3
[3,]    2    3    4

> f(4)
     [,1] [,2] [,3] [,4]
[1,]    0    1    2    3
[2,]    1    2    3    4
[3,]    2    3    4    5
[4,]    3    4    5    6

> f(5)
     [,1] [,2] [,3] [,4] [,5]
[1,]    0    1    2    3    4
[2,]    1    2    3    4    5
[3,]    2    3    4    5    6
[4,]    3    4    5    6    7
[5,]    4    5    6    7    8

Solution 5:[5]

You can use sequence:

function(n) matrix(sequence(rep(n, n), seq(n) - 1), nrow = n)

output

f <- function(n) matrix(sequence(rep(n, n), seq(n) - 1), nrow = n)
f(3)
     [,1] [,2] [,3]
[1,]    0    1    2
[2,]    1    2    3
[3,]    2    3    4

f(5)
     [,1] [,2] [,3] [,4] [,5]
[1,]    0    1    2    3    4
[2,]    1    2    3    4    5
[3,]    2    3    4    5    6
[4,]    3    4    5    6    7
[5,]    4    5    6    7    8

Solution 6:[6]

Using indexing instead of tapply will speed things up a bit. Or Rcpp:

sumdiags <- function(mat, minor = TRUE) {
  m <- ncol(mat)
  
  if (minor) {
    n <- nrow(mat)
    lens <- c(pmin(1:n, m), pmin((m - 1L):1, n))
    c(mat[1], diff(cumsum(mat[sequence(lens, c(1:n, seq(2L*n, by = n, length.out = m - 1L)), n - 1L)])[cumsum(lens)]))
  } else {
    Recall(mat[,m:1])
  }
}

# compare to tapply solution
sumdiags2 <- function(mat, minor = TRUE) {
  if (minor) {
    as.numeric(tapply(mat, row(mat) + col(mat), sum))
  } else {
    Recall(mat[,ncol(mat):1])
  }
}

# or Rcpp
Rcpp::cppFunction('NumericVector sumdiagsRcpp(const NumericMatrix& mat) {
  const int n = mat.nrow();
  const int m = mat.ncol();
  NumericVector x (n + m - 1);

  for(int row = 0; row < n; row++) {
    for(int col = 0; col < m; col++) {
      x[row + col] += mat(row, col);
    }
  }
  return x;
}')

# OP data
x <- c(0.85, 0.1, 0.05)
m <- outer(x, x)
sumdiags(m)
#> [1] 0.7225 0.1700 0.0950 0.0100 0.0025
sumdiags2(m)
#> [1] 0.7225 0.1700 0.0950 0.0100 0.0025
sumdiagsRcpp(m)
#> [1] 0.7225 0.1700 0.0950 0.0100 0.0025

# bigger matrix for benchmarking
m <- matrix(runif(1e6), 1e3)
microbenchmark::microbenchmark(sumdiags = sumdiags(m),
                               sumdiags2 = sumdiags2(m),
                               sumdiagsRcpp = sumdiagsRcpp(m),
                               check = "equal")
#> Unit: milliseconds
#>         expr       min        lq      mean    median      uq        max neval
#>     sumdiags  9.985302 10.266350 13.686723 10.803401 17.5274  22.387601   100
#>    sumdiags2 55.790402 65.140051 78.763478 67.120051 70.4165 183.936801   100
#> sumdiagsRcpp  2.192201  2.378651  2.599326  2.631751  2.7050   4.038301   100

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Rui Barradas
Solution 2
Solution 3 Allan Cameron
Solution 4
Solution 5 Maël
Solution 6