Реализация арифметической системы в R

Я начал реализовывать в R своего рода числа. У меня есть функция их сложения, умножения и т. д. Теперь я хочу сделать удобный интерфейс для арифметики над этими числами. То есть я не хочу, чтобы пользователь вводилmultiply(x, add(y, z)), ноx * (y + z)вместо этого и т. д. Как лучше всего добиться этого с точки зрения эффективности: S3 или S4? Я уже делал такую ​​арифметическую реализацию в S4 для пакета (lazyNumbers), это было немного длинно, немного «многословно». В S3 удобнее? Как быть с S3 пока не знаю, но при необходимости научусь.

2 ответа

Ответ будет зависеть от того, как действуют ваши «цифры», но я постараюсь определить здесь сильные и слабые стороны каждого подхода, чтобы вы могли принять собственное решение.

S3

  • проверяет только первый аргумент . Итак, если у вас есть объект вашего класса,x + 1и1 + xне будет вызывать тот же метод. (Обновление: очевидно, члены группы учитывают класс обоих аргументов, поэтому, если есть+.myclassилиOps.myclassфункция, то они все равно будут вызываться в случае1+xиx+1. Однако дляx+yгде существуют отдельные методы для класса иy, используется метод по умолчанию, который, предположительно, завершится неудачно.)
  • Я считаю, что это быстрее, поскольку проверок меньше, но я на самом деле это не проверял.

С4

  • проверяетclass()из всех аргументов
  • займет больше времени, так как придется просматривать всю таблицу методов, а не искать функцию с именемgeneric.class
  • для внутренних универсальных функций будет искать методы только в том случае, если хотя бы один из аргументов является объектом S4 (это не должно быть проблемой, если ваш класс S4).
  • Проверяет достоверность создаваемых объектов (по умолчанию проверяется только то, что объекты и слоты в них имеют правильный класс. Это можно переопределить, если вы хотите использоватьsetValidity(например, функция, которая всегда возвращает TRUE, чтобы пропустить проверку достоверности).

Также обратите внимание на групповые дженерики Ops,Mathи так далее. Возможно, даже если вам нужно использовать S4, вы можете просто написать для них методы. (Помните, что+и-может быть как унарным, так и бинарным, однако необходимо убедиться, что функция работает так, как задумано, для случая, когдаe1твой класс S4 иe2являетсяmissing. В зависимости от того, какой объект представляет ваш класс, «как задумано» может означать выдачу ошибки.)

С точки зрения эффективности, если вы тратите много времени на отправку метода, а не на фактические вычисления, то, вероятно, вы делаете что-то неправильно. В частности, рассмотрите возможность того, чтобы ваш класс представлял вектор (возможно, список, если вам действительно нужно) любого типа чисел, с которыми вы работаете. После выбора метода расчет займет одинаковое количество времени независимо от того, использовали ли мы S3 или S4, за исключением того, что S4 в конце проверит допустимость объекта. Проверка обычно выполняется быстрее, чем отправка метода, если только класс не очень сложен (т. е. не имеет большого количества слотов или глубокой структуры наследования).

Если под «эффективностью» вы просто имели в виду отсутствие написания большого количества кода, то групповые обобщения — лучшая экономия времени. Они работают как с S3, так и с S4.

Ниже приведен простой пример группового обобщения. Я использовал пример класса с двумя слотами, как обычный числовой иtimestampкак время было рассчитано. Мы хотим, чтобы операторы «действовали по слоту», и добиваемся этого следующим образом:

      ## 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

который производит шесть объектов классаtimestampedNumсxслоты 5, 5, -5, 6, 6 и 10 соответственно.

Просто чтобы уточнить мой комментарий ...

      x <- structure(0, class = "zzz")

.S3method("Ops", "zzz",
          function(e1, e2) {
              if (missing(e1))
                  "A" # should never happen
              else if (missing(e2))
                  "B"
              else if (!inherits(e2, "zzz"))
                  "C"
              else if (!inherits(e1, "zzz"))
                  "D"
              else "E"
          })

+x
## [1] "B"
x + 1
## [1] "C"
1 + x
## [1] "D"
x + x
## [1] "E"
Другие вопросы по тегам