在 R 中实现算术系统

Sté*_*ent 3 math r r-s3 r-s4

我开始在 R 中实现一种数字。我有一个函数可以对它们进行加法、乘法等。现在我想为这些数字的算术做一个方便的接口。也就是说,我不希望用户键入multiply(x, add(y, z)),而是x * (y + z)等等。就效率而言,实现此目的的最佳方法是什么,S3 还是 S4?我已经在 S4 中为一个包( lazyNumbers )做了这样的算术实现,这有点长,有点“冗长”。S3是不是更舒服?我还不知道如何使用 S3,但如果需要的话我会学习。

JDL*_*JDL 5

答案将取决于你的“数字”如何运作,但我将尝试找出每种方法的优点和缺点,以便你可以做出自己的决定。

S3

  • 只检查第一个class()参数的。因此,如果您有一个类的对象,并且不会调用相同的方法。(更新:显然,该组的成员确实考虑了两个参数的类,因此如果存在or函数,那么在和 的情况下仍然会调用这些函数。但是,对于和 类有单独的方法,使用默认方法,这可能会失败。)xx + 11 + xOps+.myclassOps.myclass1+xx+1x+yxy
  • 我相信它会更快,因为检查更少,但我还没有实际测试过。

S4

  • 检查class()所有参数的
  • 将花费更多时间,因为它必须查找整个方法表,而不是查找名为的函数generic.class
  • 对于内部泛型函数,仅当至少一个参数是 S4 对象时才会查找方法(如果您的类是 S4,则应该不是问题)。
  • 检查它创建的对象的有效性(默认情况下,只是其中的对象和槽具有正确的类。如果您想使用,可以覆盖它setValidity(例如,始终返回 TRUE 的函数以跳过有效性检查)。

还要查看组泛型 OpsMath。即使您需要使用 S4,您也可能只需为这些编写方法即可。(请记住,虽然+-可以是一元的,也可以是二进制的,但您需要确保该函数在e1是 S4 类 且e2是 的情况下按预期工作missing。根据您的类代表的对象类型,“按预期”可能会意味着抛出错误。)

就效率而言,如果您花费很长时间在方法调度而不是实际计算上,那么您可能做错了什么。特别是,考虑让你的类代表你正在使用的任何类型的数字的向量(如果你确实需要的话,可能是一个列表)。一旦选择了一种方法,无论我们使用 S3 还是 S4,计算都将花费相同的时间,但 S4 将在最后检查对象是否有效。检查通常比方法分派更快,除非类非常复杂(即有很多槽或很深的继承结构)。

如果“效率”只是意味着不编写大量代码,那么组泛型是最好的节省时间的方法。它们适用于 S3 和 S4。

下面是一个组泛型的简单示例。我使用了具有两个槽的类的示例,x作为普通数字和timestamp计算时间。我们希望运营商“对槽进行操作x”,我们通过以下方式实现这一点:

## define simple class based on numeric
timestampedNum <- setClass(
  "timestampedNum",
  slots=c(timestamp="POSIXct",x="numeric"),
  prototype=prototype(timestamp=Sys.time())
)
## set methods for Ops group generic
## we need four of them:
## one for unary +, -
## one for our class [op] something else
## one for something else [op] our class
## one for our class [op] our class
setMethod(
  "Ops",
  signature = signature(e1="timestampedNum",e2="missing"),
  definition = function(e1) timestampedNum(
    x=callGeneric(e1@x),
    timestamp=Sys.time()
  )
)
setMethod(
  "Ops",
  signature = signature(e1="timestampedNum",e2="ANY"),
  definition = function(e1,e2) timestampedNum(
    x=callGeneric(e1@x,e2),
  timestamp=Sys.time()
  )
)
setMethod(
  "Ops",
  signature = signature(e1="ANY",e2="timestampedNum"),
  definition = function(e1,e2) timestampedNum(
    x=callGeneric(e1,e2@x),
    timestamp=Sys.time()
  )
)
setMethod(
  "Ops",
  signature = signature(e1="timestampedNum",e2="timestampedNum"),
  definition = function(e1,e2) timestampedNum(
    x=callGeneric(e1@x,e2@x),
  timestamp=Sys.time()
  )
)

z <- timestampedNum(x=5)
z
+z
-z
z + 1
1 + z
z + z
Run Code Online (Sandbox Code Playgroud)

它生成 6 个类对象timestampedNumx插槽分别为 5、5、-5、6、6 和 10。

  • “`x + 1` 和 `1 + x` 不会调用相同的方法”......这是完全错误的。`?groupGeneric` 明确指出,即使在 S3 情况下(即,当两个参数都不是 S4 对象时),`+` 也会分派两个参数的类属性。 (2认同)