В языке R есть отличная функция для определения функций, которые могут принимать переменное количество аргументов. Например, функция data.frame
принимает любое количество аргументов, и каждый аргумент становится данными для столбца в результирующей таблице данных. Пример использования:
> data.frame(letters=c("a", "b", "c"), numbers=c(1,2,3), notes=c("do", "re", "mi"))
letters numbers notes
1 a 1 do
2 b 2 re
3 c 3 mi
Подпись функции включает в себя многоточие, например:
function (..., row.names = NULL, check.rows = FALSE, check.names = TRUE,
stringsAsFactors = default.stringsAsFactors())
{
[FUNCTION DEFINITION HERE]
}
Я хотел бы написать функцию, которая делает что-то подобное, беря несколько значений и объединяя их в одно возвращаемое значение (а также выполняя некоторую другую обработку). Для этого мне нужно выяснить, как "распаковать" ...
из аргументов функции внутри функции. Я не знаю, как это сделать. Соответствующей строкой в определении функции data.frame
является object <- as.list(substitute(list(...)))[-1L]
, о которой я не могу понять.
Итак, как я могу преобразовать многоточие из сигнатуры функции в, например, список?
Чтобы быть более конкретным, как написать get_list_from_ellipsis
в коде ниже?
my_ellipsis_function(...) {
input_list <- get_list_from_ellipsis(...)
output_list <- lapply(X=input_list, FUN=do_something_interesting)
return(output_list)
}
my_ellipsis_function(a=1:10,b=11:20,c=21:30)
Кажется, есть два возможных способа сделать это. Они as.list(substitute(list(...)))[-1L]
и list(...)
. Однако эти два не делают то же самое. (Для различий см. Примеры в ответах.) Может ли кто-нибудь сказать мне, что такое практическое различие между ними, и какой из них я должен использовать?
Я читал ответы и комментарии, и я вижу, что о нескольких вещах не упоминалось:
data.frame
использует версию list(...)
. Фрагмент кода:
object <- as.list(substitute(list(...)))[-1L]
mrn <- is.null(row.names)
x <- list(...)
object
используется для создания магии с именами столбцов, но x
используется для создания окончательного data.frame
.
Для использования неоцененного аргумента ...
смотрите код write.csv
, где используется match.call
.
Когда вы пишете в комментарии, результат ответа Dirk не является списком списков. Является списком длины 4, элементами которого являются language
. Первый объект - это symbol
- list
, второй - выражение 1:10
и т.д. Это объясняет, почему требуется [-1L]
: он удаляет ожидаемый symbol
из предоставленных аргументов в ...
(потому что он всегда является списком).
Поскольку состояния Dirk substitute
возвращает "дерево синтаксического анализа неоценимое выражение".
Когда вы вызываете my_ellipsis_function(a=1:10,b=11:20,c=21:30)
, тогда ...
"создает" список аргументов: list(a=1:10,b=11:20,c=21:30)
и substitute
делает его списком из четырех элементов:
List of 4
$ : symbol list
$ a: language 1:10
$ b: language 11:20
$ c: language 21:30
Первый элемент не имеет имени, а [[1]]
- в ответе Дирка. Я достигаю этих результатов, используя:
my_ellipsis_function <- function(...) {
input_list <- as.list(substitute(list(...)))
str(input_list)
NULL
}
my_ellipsis_function(a=1:10,b=11:20,c=21:30)
Как и выше, мы можем использовать str
для проверки того, какие объекты находятся в функции.
my_ellipsis_function <- function(...) {
input_list <- list(...)
output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
return(output_list)
}
my_ellipsis_function(a=1:10,b=11:20,c=21:30)
int [1:10] 1 2 3 4 5 6 7 8 9 10
int [1:10] 11 12 13 14 15 16 17 18 19 20
int [1:10] 21 22 23 24 25 26 27 28 29 30
$a
Min. 1st Qu. Median Mean 3rd Qu. Max.
1.00 3.25 5.50 5.50 7.75 10.00
$b
Min. 1st Qu. Median Mean 3rd Qu. Max.
11.0 13.2 15.5 15.5 17.8 20.0
$c
Min. 1st Qu. Median Mean 3rd Qu. Max.
21.0 23.2 25.5 25.5 27.8 30.0
Это нормально. Давайте посмотрим substitute
версия:
my_ellipsis_function <- function(...) {
input_list <- as.list(substitute(list(...)))
output_list <- lapply(X=input_list, function(x) {str(x);summary(x)})
return(output_list)
}
my_ellipsis_function(a=1:10,b=11:20,c=21:30)
symbol list
language 1:10
language 11:20
language 21:30
[[1]]
Length Class Mode
1 name name
$a
Length Class Mode
3 call call
$b
Length Class Mode
3 call call
$c
Length Class Mode
3 call call
Не то, что нам нужно. Вам понадобятся дополнительные трюки для работы с такими объектами (как в write.csv
).
Если вы хотите использовать ...
, тогда вы должны использовать его, как в ответе Шейна, list(...)
.
Вы можете преобразовать многоточие в список с помощью list()
, а затем выполнить следующие операции над ним:
> test.func <- function(...) { lapply(list(...), class) }
> test.func(a="b", b=1)
$a
[1] "character"
$b
[1] "numeric"
Итак, ваша функция get_list_from_ellipsis
не более чем list
.
Допустимый пример использования для этого - в случаях, когда вы хотите передать неизвестное количество объектов для работы (как в вашем примере c()
или data.frame()
). Не рекомендуется использовать ...
, когда вы знаете каждый параметр заранее, однако, поскольку он добавляет некоторую двусмысленность и дальнейшее усложнение строки аргумента (и делает сигнатуру функции непонятной для любого другого пользователя). Список аргументов является важной частью документации для пользователей функций.
В противном случае это также полезно для случаев, когда вы хотите передать параметры в подфункцию, не подвергая их всех аргументам вашей собственной функции. Это можно отметить в документации по функциям.
Просто добавьте ответы Shane и Dirk: интересно сравнить
get_list_from_ellipsis1 <- function(...)
{
list(...)
}
get_list_from_ellipsis1(a = 1:10, b = 2:20) # returns a list of integer vectors
$a
[1] 1 2 3 4 5 6 7 8 9 10
$b
[1] 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
с
get_list_from_ellipsis2 <- function(...)
{
as.list(substitute(list(...)))[-1L]
}
get_list_from_ellipsis2(a = 1:10, b = 2:20) # returns a list of calls
$a
1:10
$b
2:20
В любом случае, любая версия кажется подходящей для ваших целей в my_ellipsis_function
, хотя первая явно проще.
Ты уже дал половину ответа. Рассмотрим
R> my_ellipsis_function <- function(...) {
+ input_list <- as.list(substitute(list(...)))
+ }
R> print(my_ellipsis_function(a=1:10, b=2:20))
[[1]]
list
$a
1:10
$b
11:20
R>
Таким образом, из вызова потребовалось два аргумента a
и b
и преобразовали его в список. Разве это не то, о чем вы просили?
[[1]]
. Также мне хотелось бы узнать, как работает магическое заклинание as.list(substitute(list(...)))
.
list(...)
создает объект list
на основе аргументов. Затем substitute()
создает дерево разбора для неоцененного выражения; см. справку для этой функции. А также хороший продвинутый текст на R (или S). Это не тривиальные вещи.
Это работает так, как ожидалось. Ниже представлен интерактивный сеанс:
> talk <- function(func, msg, ...){
+ func(msg, ...);
+ }
> talk(cat, c("this", "is", "a","message."), sep=":")
this:is:a:message.
>
То же, за исключением аргумента по умолчанию:
> talk <- function(func, msg=c("Hello","World!"), ...){
+ func(msg, ...);
+ }
> talk(cat,sep=":")
Hello:World!
> talk(cat,sep=",", fill=1)
Hello,
World!
>
Как вы можете видеть, вы можете использовать это для передачи "дополнительных" аргументов функции внутри вашей функции, если значения по умолчанию не являются тем, что вы хотите в конкретном случае.
Я думаю, это то, чего вы ожидали (после рис.). a1, a2, a3, a4 - некоторые произвольные векторы, а "catt" - это функция, которая принимает любое количество аргументов и возвращает конкатенированный вектор входных аргументов.
list
иc
работают таким образом, но обе являются примитивами, поэтому я не могу легко проверить их исходный код, чтобы понять, как они работают.list()
делает именно то, что вы хотите, верно?