Here is a good example. I made 4x4, so we can easily see it, but it all sets to n . It is also fully vectorized, so it should have a good speed.
n = 4 mat = matrix(1:n^2, nrow = n) mat.pad = rbind(NA, cbind(NA, mat, NA), NA)
With a soft matrix, neighbors are simply n by n submatrices shifting around. Using compass directions as labels:
ind = 2:(n + 1) # row/column indices of the "middle" neigh = rbind(N = as.vector(mat.pad[ind - 1, ind ]), NE = as.vector(mat.pad[ind - 1, ind + 1]), E = as.vector(mat.pad[ind , ind + 1]), SE = as.vector(mat.pad[ind + 1, ind + 1]), S = as.vector(mat.pad[ind + 1, ind ]), SW = as.vector(mat.pad[ind + 1, ind - 1]), W = as.vector(mat.pad[ind , ind - 1]), NW = as.vector(mat.pad[ind - 1, ind - 1])) mat # [,1] [,2] [,3] [,4] # [1,] 1 5 9 13 # [2,] 2 6 10 14 # [3,] 3 7 11 15 # [4,] 4 8 12 16 neigh[, 1:6] # [,1] [,2] [,3] [,4] [,5] [,6] # N NA 1 2 3 NA 5 # NE NA 5 6 7 NA 9 # E 5 6 7 8 9 10 # SE 6 7 8 NA 10 11 # S 2 3 4 NA 6 7 # SW NA NA NA NA 2 3 # W NA NA NA NA 1 2 # NW NA NA NA NA NA 1
So, you can see the first element of mat[1,1] , starting from the north and going clockwise, neighbors are the first column of neigh . The next element is mat[2,1] , etc. Down the mat . (You can also compare with @mrip's answer and see that our columns have the same elements in a different order.)
Benchmarking
Small matrix
mat = matrix(1:16, nrow = 4) mbm(gregor(mat), mrip(mat), marat(mat), u20650(mat), times = 100)
In a larger matrix, I had to pull out the user20650 function because it tried to allocate a 232.8 GB vector, and I also pulled out Marat's answer after waiting about 10 minutes.
mat = matrix(1:500^2, nrow = 500) mbm(gregor(mat), mrip(mat), times = 100)
So it seems that anyway, when time matters, @mrip's solutions are by far the fastest.
Functions Used:
gregor = function(mat) { n = nrow(mat) mat.pad = rbind(NA, cbind(NA, mat, NA), NA) ind = 2:(n + 1) # row/column indices of the "middle" neigh = rbind(N = as.vector(mat.pad[ind - 1, ind ]), NE = as.vector(mat.pad[ind - 1, ind + 1]), E = as.vector(mat.pad[ind , ind + 1]), SE = as.vector(mat.pad[ind + 1, ind + 1]), S = as.vector(mat.pad[ind + 1, ind ]), SW = as.vector(mat.pad[ind + 1, ind - 1]), W = as.vector(mat.pad[ind , ind - 1]), NW = as.vector(mat.pad[ind - 1, ind - 1])) return(neigh) } mrip = function(mat) { m2<-cbind(NA,rbind(NA,mat,NA),NA) addresses <- expand.grid(x = 1:4, y = 1:4) ret <- c() for(i in 1:-1) for(j in 1:-1) if(i!=0 || j !=0) ret <- rbind(ret,m2[addresses$x+i+1+nrow(m2)*(addresses$y+j)]) return(ret) } get.neighbors <- function(rw, z, mat) { # Convert to absolute addresses z2 <- t(z + unlist(rw)) # Choose those with indices within mat b.good <- rowSums(z2 > 0)==2 & z2[,1] <= nrow(mat) & z2[,2] <= ncol(mat) mat[z2[b.good,]] } marat = function(mat) { n.row = n.col = nrow(mat) addresses <- expand.grid(x = 1:n.row, y = 1:n.col) # Relative addresses z <- rbind(c(-1,0,1,-1,1,-1,0,1), c(-1,-1,-1,0,0,1,1,1)) apply(addresses, 1, get.neighbors, z = z, mat = mat) # Returns a list with neighbors } u20650 = function(mat) { w <- which(mat==mat, arr.ind=TRUE) d <- as.matrix(dist(w, "maximum", diag=TRUE, upper=TRUE)) # extract neighbouring values for each element # extract where max distance is one a <- apply(d, 1, function(i) mat[i == 1] ) names(a) <- mat return(a) }