有几种方法可以解决这个问题,一些是有效的,一些是可读的(主观的),不是很多都是两者兼而有之。
例如,您可以递归地执行此操作,如下所示:
pairwise_recur <- function(n, start = 1) {
if (n == start) return()
nrows <- factorial(n) / (factorial(2) * factorial(n-2))
res <- matrix(nrow = nrows, ncol = 2)
rbind(
cbind(rep(start, times = n - start),
1 + start:(n-1)),
pairwise_recur(n, start = start + 1)
)
}
pairwise_recur(4)
# [,1] [,2]
# [1,] 1 2
# [2,] 1 3
# [3,] 1 4
# [4,] 2 3
# [5,] 2 4
# [6,] 3 4
但有几件事效率较低:
- R 没有很好地进行尾递归,所以理论上这可能会填满调用堆栈并耗尽 R;和
- 这是我在关于迭代调用的评论中建议不要做的事情。
rbind
- 它很容易出错:如果你用
n < start
or调用n==0
,那么它会失败。
很可能:
- 如果你不能以
factorial
这种方式使用,你可以用prod(1:n)
. 下面的其余功能将使用此prod
方法,交给您首选。
- 两者
factorial
都会prod
以非常高的速度开始失败n
,可能远远超出您将用于此作业的限制。在这些数字上,可能有必要进入gamma
更高效的高阶乘计算领域n
(并且可能需要在 R 完全支持 64 位整数之前)。
修复其中一些问题的迭代可能是
pairwise_iter <- function(n) {
nrows <- prod(1:n) / ( prod(1:2) * prod(1:(n-2)) )
res <- matrix(nrow = nrows, ncol = 2)
r <- 0
for (i in 1:(n-1)) {
for (j in (i+1):n) {
r <- r + 1
res[r,1] <- i
res[r,2] <- j
}
}
res
}
# same output
r
坦率地说,可以通过一些巧妙的数学运算来摆脱计数器i
和j
。
但它仍然容易出现问题n < 3
。这可以通过以下方式缓解:
pairwise_iter2 <- function(n) {
if (n <= 1) return(matrix(nrow = 0, ncol = 2))
nrows <- prod(seq_len(n)) / ( prod(1:2) * prod(seq_len(n-2)) )
res <- matrix(nrow = nrows, ncol = 2)
r <- 0
for (i in 1:(n-1)) {
for (j in (i+1):n) {
r <- r + 1
res[r,1] <- i
res[r,2] <- j
}
}
res
}
pairwise_iter2(0)
# [,1] [,2]
pairwise_iter2(1)
# [,1] [,2]
pairwise_iter2(2)
# [,1] [,2]
# [1,] 1 2
pairwise_iter2(3)
# [,1] [,2]
# [1,] 1 2
# [2,] 1 3
# [3,] 2 3
一个区别(由前导if
/预先减轻return
)是使用seq_len
: 如果你想要一个长度序列n
,那么1:n
只有n >= 1
. 如果n
为 0,则1:0
生成长度为 2 的向量,这不是您应该得到的;而是seq_len(0)
返回一个长度为 0 的向量,这样更加一致。
在 R 的做事方式中,这仍然不是“有效的”。为此,您可以删除内部for
循环并按向量分配:
pairwise_vec1 <- function(n) {
if (n <= 1) return(matrix(nrow = 0, ncol = 2))
nrows <- prod(seq_len(n)) / ( prod(1:2) * prod(seq_len(n-2)) )
res <- matrix(nrow = nrows, ncol = 2)
r <- 0
for (i in 1:(n-1)) {
vec <- seq_len(n - i)
res[r + vec, 1] <- i
res[r + vec, 2] <- i + vec
r <- r + length(vec)
}
res
}
实际上,甚至可以在没有外部for
循环的情况下生成它,但是它需要更多的矢量化魔法,这既超出了本作业的范围,也超出了我的时间来学习本课。