You could sample a random number from the integer sequence 1:n
, n
times, but each time exclude the cell number that corresponds to the diagonal.
One way to do this is as follows:
n <- 5
rnd <- sample(n-1, repl=T)
i <- c(rnd + (rnd >= seq_len(n-1)) * 1, sample(n-1, 1))
i
# [1] 3 3 4 5 1
Here, each element of i
is prevented from being equal to the element's index (e.g. element 1 cannot be 1, element 2 cannot be 2, etc.). We can consider each element of i
to be the selected column for each row in turn.
Next we set up a matrix of zeroes, and (for the above example) fill the cells [1, 3]
, [2, 3]
, [3, 4]
, [4, 5]
and [5, 1]
to 1.
m <- matrix(0, nc=n, nr=n)
m[cbind(seq_len(n), i)] <- 1
m
# [,1] [,2] [,3] [,4] [,5]
# [1,] 0 0 1 0 0
# [2,] 0 0 1 0 0
# [3,] 0 0 0 1 0
# [4,] 0 0 0 0 1
# [5,] 1 0 0 0 0
The final row of code uses matrix subsetting to subset the matrix m
to the relevant cells.
EDIT
In order to ensure that each column and each row have only a single 1
, and that these are kept off the diagonal, the following vectorised approach will work. The trick here is to permute the vector n-1
, and consider each element of that permuted vector to be the index, for each row, of the n-1
non-diagonal elements that we will assign 1
. We then check, for each element of our permuted vector, whether the value is less than the index of the diagonal for the corresponding row. If so, we leave the element as is, otherwise we add 1. This determines the column indices for the first n-1
rows. The index for the final row is then simply the index of the column that does not yet have a 1
.
n <- 5
rnd <- sample(n-1)
i <- union(rnd + (rnd >= seq_len(n-1)) * 1, seq_len(n))
m <- matrix(0, nc=n, nr=n)
m[cbind(seq_len(n), i)] <- 1
m
# [,1] [,2] [,3] [,4] [,5]
# [1,] 0 0 1 0 0
# [2,] 0 0 0 0 1
# [3,] 0 0 0 1 0
# [4,] 1 0 0 0 0
# [5,] 0 1 0 0 0