该类的整个设计rational
似乎建立在保留其类型安全性和对用户隐藏实现的基础上,我可以看到这对于让它始终如一地工作是必要的,但这意味着你不能指望它与 R 的默认设置很好地配合S3 方法。
帮助文件vctrs
专门说
- dims()、dims<-、dimnames()、dimnames<-、levels() 和 levels<- 方法会引发错误。
这表明 的作者vctrs
并不认为它是构建矩阵方法的一个很好的基础。
在任何情况下,我都不会急于尝试将其放入矩阵中,因为一旦它存在,您将无法对其进行任何操作:您没有可用的算术方法:
x + 2
#> Error: <rational> + <double> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.
x * 2
#> Error: <rational> * <double> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.
x + x
#> Error: <rational> + <rational> is not permitted
#> Run `rlang::last_error()` to see where the error occurred.
所以你需要先定义算术方法。在你这样做之前,你需要$
一个用于分子和分母的访问器,一个is.rational
在尝试算术之前检查类型的函数,一个找到最大公分母的函数,以及一个基于它简化你的有理数的函数。
`$.vctrs_rational` <- function(vec, symb) unclass(vec)[[as.character(symb)]]
is.rational <- function(num) class(num)[1] == "vctrs_rational"
gcd <- function(x, y) ifelse(x %% y, gcd(y, x %% y), y)
simplify <- function(num) {
common <- gcd(num$n, num$d)
rational(num$n / common, num$d/common)
}
所以现在你可以做
x$n
#> [1] 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
x$d
#> [1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
is.rational(x)
#> [1] TRUE
现在编写算术函数。例如,这是一个涵盖数字和有理类型的基本算术实现:
Ops.vctrs_rational <- function(vec, num)
{
if(!is.rational(vec)) {tmp <- vec; vec <- num; num <- tmp; }
if(.Generic == '*'){
if(is.rational(num)) return(simplify(rational(vec$n * num$n, vec$d * num$d)))
else return(simplify(rational(vec$n * 2, vec$d)))
}
else if (.Generic == '/'){
if(is.rational(num)) return(vec * rational(num$d, num$n))
else return(vec * rational(1, num))
}
else if (.Generic == '+'){
if(is.rational(num)){
new_n <- vec$n * (vec$d * num$d)/vec$d + num$n * (vec$d * num$d)/num$d
return(simplify(rational(new_n, vec$d * num$d)))
}
else return(simplify(rational(num * vec$d + vec$n, vec$d)))
}
else if (.Generic == '-'){
if(is.rational(num)) return(vec + rational(-num$n, num$d))
else return(vec + (-num))
}
else if (.Generic == '^'){
if(is.rational(num) | num < 0) stop("fractional and negative powers not supported")
return(simplify(rational(vec$n ^ num, vec$d ^ num)))
}
}
这现在允许您执行以下操作,例如:
x * 3
#> <rational[15]>
#> [1] 3/1 3/2 1/1 3/4 3/5 1/2 3/7 3/8 1/3 3/10 3/11 1/4 3/13 3/14 1/5
x + x
#> <rational[15]>
#> [1] 2/1 1/1 2/3 1/2 2/5 1/3 2/7 1/4 2/9 1/5 2/11 1/6 2/13 1/7 2/15
(2 + x)^2 / (3 * x + 1)
#> <rational[15]>
#> [1] 3/1 25/8 49/15 27/8 121/35 169/48 25/7 289/80
#> [9] 361/99 147/40 529/143 625/168 243/65 841/224 961/255
尝试matrix()
直接使用它自己可能是行不通的,因为matrix
通过转换为基向量然后调用 C 代码来工作。这去除了类信息。
这意味着您需要定义一个单独的rational_matrix
类,这反过来又会从支持rational_vector
类中受益。然后我们可以定义具体的格式和打印方法:
as.vector.vctrs_rational <- function(x, ...) {
n <- x$n/x$d
attr(n, "denom") <- x$d
attr(n, "numerator") <- x$n
class(n) <- "rational_attr"
n
}
rational_matrix <- function(data, nrow = 1, ncol = 1,
byrow = FALSE, dimnames = NULL){
d <- as.vector(data)
m <- .Internal(matrix(d, nrow, ncol, byrow, dimnames, missing(nrow),
missing(ncol)))
m_dim <- dim(m)
attributes(m) <- attributes(d)
dim(m) <- rev(m_dim)
class(m) <- c("rational_matrix", "matrix")
m
}
format.rational_matrix <- function(x) {
return(paste0(attr(x, "numerator"), "/", attr(x, "denom")))
}
print.rational_matrix <- function(x)
{
print(matrix(format(x), nrow = dim(x)[2]), quote = FALSE)
}
最后,您需要覆盖matrix()
以使其成为 S3 方法,确保首先将函数复制为matrix.default
matrix.default <- matrix
matrix <- function(data = NA, ...) UseMethod("matrix")
matrix.vctrs_rational <- function(data, ...) rational_matrix(data, ...)
所以现在你可以这样做:
matrix(x, nrow = 5)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 1/1 1/4 1/7 1/10 1/13
#> [2,] 1/2 1/5 1/8 1/11 1/14
#> [3,] 1/3 1/6 1/9 1/12 1/15
rational_matrix(x + 5, nrow = 3)
#> [,1] [,2] [,3] [,4] [,5]
#> [1,] 6/1 21/4 36/7 51/10 66/13
#> [2,] 11/2 26/5 41/8 56/11 71/14
#> [3,] 16/3 31/6 46/9 61/12 76/15
rational_matrix(x + x, nrow = 5)
#> [,1] [,2] [,3]
#> [1,] 2/1 1/3 2/11
#> [2,] 1/1 2/7 1/6
#> [3,] 2/3 1/4 2/13
#> [4,] 1/2 2/9 1/7
#> [5,] 2/5 1/5 2/15
然而,为了让它工作,无论如何我们都必须添加带有属性的额外类,所以我的感觉是,如果你想要一个与矩阵等一起工作的理性类,你应该在本机 S3 或其他面向对象方法之一中进行在 R 中可用,而不是使用vctrs
包。
值得一提的是,上面的类还远未准备好生产,因为您需要添加测试相等/不等式的方法、描述矩阵运算的方法、转换为十进制的能力、绘图方法等。