Кэширование среднего вектора в R

Я изучаю R и наткнулся на некоторый код как часть практического задания.

 makeVector <- function(x = numeric()) {
         m <- NULL
         set <- function(y) {
                x <<- y
                m <<- NULL
        }
        get <- function() x
        setmean <- function(mean) m <<- mean
        getmean <- function() m
        list(set = set, get = get,
             setmean = setmean,
             getmean = getmean)
 }

В документации сказано:

Функция, makeVector создает специальный "вектор", который на самом деле представляет собой список, содержащий функцию

  1. установить значение вектора
  2. получить значение вектора
  3. установить значение среднего
  4. получить значение среднего

Но я не могу понять, как работает эта функция, за исключением того, что она присваивает среднее значение переменной m в этой конкретной среде.

3 ответа

Решение

m <- NULL начинается с установки значения NULL в качестве заполнителя для будущего значения

set <- function(y) {x <<- y; m <<- NULL} определяет функцию для установки вектора, xк новому вектору, yи сбрасывает среднее значение, m, чтобы NULL

get <- function() x возвращает вектор, x

setmean <- function(mean) m <<- mean устанавливает среднее значение, m, чтобы mean

getmean <- function() m возвращает среднее значение, m

list(set = set, get = get,setmean = setmean,getmean = getmean) возвращает "специальный вектор", содержащий все только что определенные функции

Этот ответ является отрывком из статьи, которую я первоначально написал в 2016 году в качестве наставника сообщества в курсе по программированию в Университете Джонса Хопкинса: Демистификация makeVector().

Общий дизайн makeVector() и cachemean()

Файл cachemean.R содержит две функции: makeVector() а также cachemean(), Первая функция в файле, makeVector() создает объект R, который хранит вектор и его среднее значение. Вторая функция, cachemean() требует аргумент, который возвращается makeVector() чтобы извлечь среднее значение из кэшированного значения, которое хранится в makeVector() окружение объекта.

Что происходит в makeVector()?

Ключевая концепция для понимания в makeVector() заключается в том, что он создает набор функций и возвращает функции из списка в родительскую среду. То есть,

    myVector <- makeVector(1:15)

приводит к объекту, myVector, который содержит четыре функции: set(), get(), setmean(), а также getmean(), Он также включает в себя два объекта данных, x а также m,

Из-за лексического обзора, myVector содержит полную копию среды для makeVector() включая любые объекты, которые определены в makeVector() во время разработки (то есть, когда это было закодировано). Диаграмма иерархии среды проясняет, что доступно внутри myVector,

Иллюстрированная в виде иерархии, глобальная среда содержит makeVector() среда. Все остальное содержимое присутствует в makeVector() окружающей среды, как показано ниже.

Поскольку каждая функция имеет свое собственное окружение в R, иерархия иллюстрирует, что объекты x и m являются родственными элементами четырех функций, get(), set(), getmean(), а также setmean(),

После запуска функции и объекта типа makeVector() создается (то есть создается), среда, содержащая myVector выглядит следующим образом:

Обратите внимание, что объект x содержит вектор 1:15, даже если myVector$set() не был выполнен Это так, потому что значение 1:15 был передан в качестве аргумента в makeVector() функция. Чем объясняется такое поведение?

Когда функция R возвращает объект, содержащий функции, в свою родительскую среду (как в случае вызова типа myVector <- makeVector(1:15)), не только делает myVector иметь доступ к определенным функциям в своем списке, но он также сохраняет доступ ко всей среде, определенной makeVector() включая исходный аргумент, использованный для запуска функции.

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

Эта функция объясняет, почему x (аргумент, инициализированный при исходном вызове функции) доступен при последующих вызовах функций на myVector такие как myVector$get(), и это также объясняет, почему код работает без явной выдачи myVector$set() установить значение x,

makeVector() шаг за шагом

Теперь давайте разберем поведение функции, шаг за шагом.

Шаг 1: Инициализация объектов

Первое, что происходит в функции, это инициализация двух объектов, x а также m,

makeVector(x = numeric()) {
  m <- NULL
  ...
}

Заметить, что x инициализируется как аргумент функции, поэтому дальнейшая инициализация в функции не требуется. m устанавливается в NULL, инициализируя его как объект в среде makeVector() для использования последующим кодом в функции.

Кроме того, формальная часть объявления функции определяет значение по умолчанию x как пустой числовой вектор. Инициализация вектора со значением по умолчанию важна, потому что без значения по умолчанию, data <- x$get() генерирует следующее сообщение об ошибке.

 Error in x$get() : argument "x" is missing, with no default

Шаг 2. Определите "поведение" или функции для объектов типа makeVector()

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

Первый makeVector() определяет set() функция. Большая часть "магии" в makeVector() происходит в set() функция.

set <- function(y) {
    x <<- y
    m <<- NULL
}

set() принимает аргумент, который называется y, Предполагается, что это значение является числовым вектором, но не указано непосредственно в формальных функциях. Для целей set() функция, не имеет значения, называется ли этот аргумент y, aVector или любое имя объекта, кроме x, Зачем? Так как есть x объект уже определен в makeVector() использование того же имени объекта усложнит понимание кода.

В set() мы используем <<- форма оператора присваивания, которая присваивает значение в правой части оператора объекту в родительской среде, названному объектом в левой части оператора.

когда set() выполняется, он делает две вещи:

  1. Назначьте входной аргумент x объект в родительской среде, и
  2. Назначьте значение NULL для m объект в родительской среде. Эта строка кода очищает любое значение m который был кэширован предыдущим исполнением cachemean(),

Поэтому, если уже есть действительное среднее значение, кэшированное в m, всякий раз, когда x сбрасывается, значение m кешируемый в памяти объект очищается, заставляя последующие вызовы cachemean() пересчитать среднее значение, а не извлекать неправильное значение из кэша.

Обратите внимание, что две строки кода в set() сделать то же самое, что и первые две строки в основной функции: установить значение x и NULL значение m,

Во-вторых, makeVector() определяет геттер для вектора x,

get <- function() x

Опять же, эта функция использует преимущества лексических функций в R. Так как символ x не определяется внутри get() R извлекает его из родительской среды makeVector(),

В третьих, makeVector() определяет сеттер для среднего m,

setmean <- function(mean) m <<- mean

поскольку m определяется в родительской среде, и мы должны получить к нему доступ после setmean() завершает, код использует <<- форма оператора присваивания для ввода входного аргумента в значение m в родительской среде.

В заключение, makeVector() определяет получатель для среднего m, Так же, как добытчик для x, R использует лексическую область видимости, чтобы найти правильный символ m чтобы получить его значение.

getmean <- function() m

На данный момент у нас есть геттеры и сеттеры, определенные для обоих объектов данных в нашем makeVector() объект.

Шаг 3. Создайте новый объект, вернув список ()

Вот другая часть "магии" в операциях makeVector() функция. Последний раздел кода назначает каждую из этих функций как элемент внутри list() и возвращает его в родительскую среду.

list(set = set, get = get,
     setmean = setmean,
     getmean = getmean)

Когда функция заканчивается, она возвращает полностью сформированный объект типа makeVector() для использования в нисходящем R-коде. Еще одна важная тонкость этого кода заключается в том, что каждый элемент в списке назван. То есть каждый элемент в списке создается с elementName = value синтаксис, следующим образом:

    list(set = set,          # gives the name 'set' to the set() function defined above
         get = get,          # gives the name 'get' to the get() function defined above
         setmean = setmean,  # gives the name 'setmean' to the setmean() function defined above
         getmean = getmean)  # gives the name 'getmean' to the getmean() function defined above

Наименование элементов списка - это то, что позволяет нам использовать $ форма оператора извлечения для доступа к функциям по имени, а не с помощью [[ форма оператора извлечения, как в myVector[[2]](), чтобы получить содержимое вектора.

Здесь важно отметить, что cachemean() функция ТРЕБУЕТ входной аргумент типа makeVector(), Если передать функции регулярный вектор, как в

 aResult <- cachemean(1:15)

вызов функции завершится с ошибкой, объясняющей, что cachemean() не удалось получить доступ $getmean() на входном аргументе, потому что $ не работает с атомными векторами. Это точно, потому что примитивный вектор не является списком и не содержит $getmean() функция, как показано ниже.

> aVector <- 1:10
> cachemean(aVector)
Error in x$getmean : $ operator is invalid for atomic vectors

Объяснение cachemean()

Без cachemean(), makeVector() функция неполная. Зачем? Как задумано, cachemean() требуется для заполнения или извлечения среднего из объекта типа makeVector(),

cachemean <- function(x, ...) {
     ...

подобно makeVector(), cachemean() начинается с одного аргумента, x и многоточие, которое позволяет вызывающей стороне передавать дополнительные аргументы в функцию.

Затем функция пытается извлечь среднее значение из объекта, переданного в качестве аргумента. Во-первых, это вызывает getmean() функция на объекте ввода.

     m <- x$getmean()

Затем он проверяет, является ли результат NULL, поскольку makeVector() устанавливает кешированное среднее значение NULL всякий раз, когда в объект устанавливается новый вектор, если значение здесь не равно NULL, у нас есть допустимое кэшированное среднее и мы можем вернуть его в родительскую среду

     if(!is.null(m)) {
          message("getting cached data")
          return(m)
     }

Если результат !is.null(m) является FALSE, cachemean() получает вектор из входного объекта, вычисляет mean(), использует setmean() функция на входном объекте, чтобы установить среднее значение во входном объекте, а затем возвращает значение среднего значения в родительскую среду путем печати среднего объекта.

     data <- x$get()
     m <- mean(data, ...)
     x$setmean(m)
     m

Обратите внимание, что cachemean() это единственное место, где mean() функция выполняется, поэтому makeVector() неполный без cachemean(),

Соединение частей: как работают функции во время выполнения

Теперь, когда мы объяснили дизайн каждой из этих функций, вот иллюстрация того, как они работают при использовании в R-скрипте.

  aVector <- makeVector(1:10)
  aVector$get()               # retrieve the value of x
  aVector$getmean()           # retrieve the value of m, which should be NULL
  aVector$set(30:50)          # reset value with a new vector
  cachemean(aVector)          # notice mean calculated is mean of 30:50, not 1:10
  aVector$getmean()           # retrieve it directly, now that it has been cached

Вывод: что заставляет работать cachemean()?

Подводя итог, можно сказать, что лексическое определение объема в R-программировании использует преимущества лексической области видимости и тот факт, что функции, возвращающие объекты типа list() также разрешить доступ к любым другим объектам, определенным в среде исходной функции. В конкретном случае makeVector() это означает, что последующий код может получить доступ к значениям x или же m за счет использования геттеров и сеттеров. Вот как cachemean() может рассчитать и сохранить среднее значение для входного аргумента, если он имеет тип makeVector(), Потому что элементы списка в makeVector() определены с именами, мы можем получить доступ к этим функциям с $ Форма оператора извлечения.

Для дополнительного комментария, объясняющего, как назначение использует функции объектной системы S3, просмотрите makeCacheMatrix() как объект.

Приложение А. В чем смысл этого задания?

После того, как студенты выполнят задание, они часто задают вопросы о его ценности и цели. Хорошей статьей, объясняющей ценность лексического анализа в статистических вычислениях, является Lexical Scoping and Statistical Computing, написанная Робертом Джентльменом и Россом Ихакой в ​​Оклендском университете.

Приложение B: cachemean.R

Вот полный список для cachemean.R.

makeVector <- function(x = numeric()) {
     m <- NULL
     set <- function(y) {
          x <<- y
          m <<- NULL
     }
     get <- function() x
     setmean <- function(mean) m <<- mean
     getmean <- function() m
     list(set = set, get = get,
          setmean = setmean,
          getmean = getmean)
}
cachemean <- function(x, ...) {
     m <- x$getmean()
     if(!is.null(m)) {
          message("getting cached data")
          return(m)
     }
     data <- x$get()
     m <- mean(data, ...)
     x$setmean(m)
     m
}

Приложение C: Часто задаваемые вопросы

Q: почему нет cachemean() вернуть кешированное значение? Мой код выглядит так:

 cachemean(makeVector(1:100))
 cachemean(makeVector(1:100))

A: Код, написанный таким образом, создает два разных объекта типа makeVector() так что два звонка cachemean() инициализировать средства каждого экземпляра, а не кэшировать и извлекать из одного экземпляра. Другой способ проиллюстрировать, как работает вышеуказанный код, заключается в следующем.

Обратите внимание, как первый звонок cachemean() устанавливает кеш, а второй вызов извлекает данные из него.

Q: почему set() никогда не используется в коде?

A: set() включен так, что когда-то объект типа makeVector() создается, его значение может быть изменено без инициализации другого экземпляра объекта. Это ненужный в первый раз объект типа makeVector() создается экземпляр. Зачем? Во-первых, значение x устанавливается в качестве аргумента функции, как в makeVector(1:30), Затем первая строка кода в наборе функций m <- NULL, одновременно выделяя память для m и установив его NULL, Когда ссылка на этот объект передается в родительскую среду при завершении функции, оба x а также m доступны для доступа по соответствующим функциям get и set.

Следующий код иллюстрирует использование set(),

Q: почему x установить со значением по умолчанию в makeVector()?

A: С x является аргументом, единственное место, где можно установить для него значение по умолчанию, - в формальных. Тип ошибки, возвращаемой cachemean() когда значение по умолчанию не установлено,

  Error in x$get() : argument "x" is missing, with no default

нежелательно. Наш код должен напрямую обрабатывать состояния ошибок, а не полагаться на основную обработку ошибок в R.

Совершенно верно создать объект типа makeVector() без заполнения его значения во время инициализации. makeVector() включает в себя функцию установки, так что можно установить его значение после создания объекта. Тем не менее, объект должен иметь действительные данные, числовой вектор, до выполнения cachemean(),

В идеале, cachemean() будет включать в себя логику, чтобы подтвердить, что x не является пустым до вычисления среднего. Настройка по умолчанию x позволяет cachemean() возвращать NaN, что является разумным результатом.

Рекомендации

  1. Чи, Яу - именной список участников R-Tutor, получено 20 июля 2016 г.
  2. Уикхем, Хэдли - Функции Advanced-R, полученные 17 июля 2016 г.
  3. Уикхем, Хэдли - Расширенные-R Scoping Issues, полученные 17 июля 2016 года.

Я думаю, что один хороший способ понять этот пример - попробовать следующее:

Сначала убедитесь, что при использовании функции make_Vector у вас есть четыре различных параметра

> mvec <- makeVector()
> x <- 1:4
> mvec$set(x)
> mvec$get()
> [1] 1 2 3 4
> mvec$getmean()
> NULL
> mvec$setmean(3.4)
> mvec$getmean()
> 3.4

3.4 Это неправильное среднее значение, я поставил эти цифры, и вы можете проверить, можете ли вы установить любое число по вашему желанию.

Вторая часть задания состоит в следующем:

cachemean <- function(x, ...) {
        m <- x$getmean()
        if(!is.null(m)) {
                message("getting cached data")
                return(m)
        }
        data <- x$get()
        m <- mean(data, ...)
        x$setmean(m)
        m
}

Эти части или код проверяют, имеется ли у вас среднее значение вектора интереса. Если они существуют, вам не нужно вычислять, и вы можете использовать переменную кеша.

 Я ввел неправильное число для среднего, тогда вы можете видеть, что я уже установил среднее значение следующим образом:

> cachemean(mvec)
> 3.4

Вы должны передать оригинальный список mvec, использованный в примере

Другие вопросы по тегам