Кэширование среднего вектора в 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
создает специальный "вектор", который на самом деле представляет собой список, содержащий функцию
- установить значение вектора
- получить значение вектора
- установить значение среднего
- получить значение среднего
Но я не могу понять, как работает эта функция, за исключением того, что она присваивает среднее значение переменной 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()
выполняется, он делает две вещи:
- Назначьте входной аргумент
x
объект в родительской среде, и - Назначьте значение 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
, что является разумным результатом.
Рекомендации
- Чи, Яу - именной список участников R-Tutor, получено 20 июля 2016 г.
- Уикхем, Хэдли - Функции Advanced-R, полученные 17 июля 2016 г.
- Уикхем, Хэдли - Расширенные-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, использованный в примере