Search code examples
rmatrixseq

Putting a sequence into a matrix around existing values


I want to generate a symmetric matrix around a diagonal of zeroes and a predetermined sequence around them. In theory the lines should show as

0 1 3 5 7 9
1 0 3 5 7 9

I've tried tweaking with the conditionals, but I suspect that it's wonky because of indexing, which I am nowhere near skilled enough to fix.

bend <- function(n){
  m <- seq(1, n, by=2)
  a <- length(m)
  y <- matrix(nrow= a, ncol = a, byrow= TRUE)
  y <- ifelse(row(y) == col(y), 0, m)
  y
}

Assuming that the input is a 9, expected output is

0 1 3 5 7 9    
1 0 3 5 7 9 
1 3 0 5 7 9    
1 3 5 0 7 9    
1 3 5 7 0 9    
1 3 5 7 9 0

Actual output is

0 3 5 7 9 1    
3 0 7 9 1 3    
5 7 0 1 3 5    
7 9 1 0 5 7    
9 1 3 5 0 9    
1 3 5 7 9 0

Solution

  • There's a simpler way to do what you need. You can start off by creating a matrix of length(x) + 1 columns and rows with all elements as a logical TRUE. Then make the diagonal FALSE using diag(). Now you can replace the TRUEs with your desired vector. The diagonal being FALSE is not affected. Since the values are replaced column-wise you need a final transpose t() to get correct result.

    This way, you don't need to worry about tracking indices.

    x <- c(1,3,5,7,9)
    
    make_matrix <- function(x) {
      m <- matrix(TRUE, ncol = length(x) + 1, nrow = length(x) + 1)
      diag(m) <- FALSE
      m[m] <- x
      t(m)
    }
    
    make_matrix(x)
    
         [,1] [,2] [,3] [,4] [,5] [,6]
    [1,]    0    1    3    5    7    9
    [2,]    1    0    3    5    7    9
    [3,]    1    3    0    5    7    9
    [4,]    1    3    5    0    7    9
    [5,]    1    3    5    7    0    9
    [6,]    1    3    5    7    9    0
    

    Here's another way with sapply. This creates the necessary row elements in each iteration and puts them in a matrix by column. Again, you need a t() to get correct results. -

    sapply(0:length(x), function(a) append(x, 0, after = a)) %>% t()
    
         [,1] [,2] [,3] [,4] [,5] [,6]
    [1,]    0    1    3    5    7    9
    [2,]    1    0    3    5    7    9
    [3,]    1    3    0    5    7    9
    [4,]    1    3    5    0    7    9
    [5,]    1    3    5    7    0    9
    [6,]    1    3    5    7    9    0
    

    Benchmarks -

    sapply is slower, likely because it's creating the matrix elements one row at a time and calls append for every row. All this overhead is avoided in the make_matrix() approach.

    x <- sample(100)
    
    microbenchmark(
      make_matrix = make_matrix(x),
      sapply = t(sapply(0:length(x), function(a) append(x, 0, after = a))),
      akrun_forloop = {
        n <- length(x) + 1
        m1  <- matrix(0, n, n)
        for(i in seq_len(nrow(m1))) m1[i, -i] <- x
      },
      times = 1000
    )
    
    Unit: microseconds
              expr      min        lq      mean   median        uq       max neval
       make_matrix  111.495  117.5610  128.3135  126.890  135.7540   225.323  1000
            sapply  520.620  551.1765  592.2642  573.335  602.2585 10477.221  1000
     akrun_forloop 3380.292 3526.3080 3837.1570 3648.765 3812.5075 20943.245  1000