Как сгенерировать автоинкрементный идентификатор в R

Я ищу эффективный способ создания уникальных числовых идентификаторов для некоторых синтетических данных, которые я генерирую.

Прямо сейчас у меня просто есть функция, которая испускает и увеличивает значение из глобальной переменной (см. демонстрационный код ниже). Однако это грязно, потому что я должен инициализировать переменную idCounter, и я бы предпочел не использовать глобальные переменные, если это возможно.

# Emit SSN
idCounter = 0
emitID = function(){
  # Turn into a formatted string
  id = formatC(idCounter,width=9,flag=0,format="d")

  # Increment id counter
  idCounter <<- idCounter+1

  return(id)
}
record$id = emitID()

Пакет uuid обеспечивает функциональность, близкую к тому, что я хочу, но мне нужны идентификаторы только для целых чисел. Какие-либо предложения? Возможно, способ конвертировать значение UUID в числовое значение какого-то типа? Очевидно, произойдут некоторые столкновения, но, вероятно, все будет в порядке. Я думаю, что, самое большее, мне понадобится 1 миллиард значений.

Спасибо за любые предложения!

Роб

Ответ 1

Неглобальная версия счетчика использует лексическую область для инкапсуляции idCounter с помощью функции increment

emitID <- local({
    idCounter <- -1L
    function(){
        idCounter <<- idCounter + 1L                     # increment
        formatC(idCounter, width=9, flag=0, format="d")  # format & return
    }
})

а затем

> emitID()
[1] "000000000"
> emitID1()
[1] "000000001"
> idCounter <- 123   ## global variable, not locally scoped idCounter
> emitID()
[1] "000000002"

Интересной альтернативой является использование шаблона 'factory' для создания независимых счетчиков. Ваш вопрос подразумевает, что вы назовете эту функцию миллиардом (hmm, не уверен, где я получил это впечатление...) раз, так что, может быть, имеет смысл векторизовать вызов formatC путем создания буфера идентификаторов?

idFactory <- function(buf_n=1000000) {
    curr <- 0L
    last <- -1L
    val <- NULL
    function() {
        if ((curr %% buf_n) == 0L) {
            val <<- formatC(last + seq_len(buf_n), width=9, flag=0, format="d")
            last <<- last + buf_n
            curr <<- 0L
        }
        val[curr <<- curr + 1L]
    }
}
emitID2 <- idFactory()

а затем (emitID1 является экземпляром версии локальной переменной выше).

> library(microbenchmark)
> microbenchmark(emitID1(), emitID2(), times=100000)
Unit: microseconds
      expr    min     lq median     uq      max neval
 emitID1() 66.363 70.614 72.310 73.603 13753.96 1e+05
 emitID2()  2.240  2.982  4.138  4.676 49593.03 1e+05
> emitID1()
[1] "000100000"
> emitID2()
[1] "000100000"

(прото-решение примерно в 3 раза медленнее, чем emitID1, хотя скорость - это не все).

Ответ 2

Мне нравится использовать пакет proto для небольшого программирования OO. Под капотом он использует среды так же, как иллюстрировал Мартин Морган.

# this defines your class
library(proto)
Counter <- proto(idCounter = 0L)
Counter$emitID <- function(self = .) {
   id <- formatC(self$idCounter, width = 9, flag = 0, format = "d")
   self$idCounter <- self$idCounter + 1L
   return(id)
}

# This creates an instance (or you can use `Counter` directly as a singleton)
mycounter <- Counter$proto()

# use it:
mycounter$emitID()
# [1] "000000000"
mycounter$emitID()
# [1] "000000001"