ЗАДАЧА
Учитывая список списков, моя цель - отменить его структуру (язык R).
Итак, я хочу, чтобы элементы вложенных списков были элементами списка первого уровня.
Вероятно, пример лучше указывает мою цель. Дано:
z <- list(z1 = list(a = 1, b = 2, c = 3), z2 = list(b = 4, a = 1, c = 0))
Я хочу, чтобы результат был эквивалентен последующему объекту R:
o <- list(a = list(z1 = 1, z2 = 1), b = list(z1 = 2, z2 = 4), c = list(z1 = 3, z2 = 0))
SOLUTIONS
МОЕ РЕШЕНИЕ
Я создал свое собственное решение, которое я прилагаю ниже, но дайте мне знать, если есть что-то лучше.
revert_list_str_1 <- function(ls) {
res <- lapply(names(ls[[1]]), function(n, env) {
name <- paste(n, 'elements', sep = '_')
assign(name, vector('list', 0))
inner <- sapply(ls, function(x) {
assign(name, c(get(name), x[which(names(x) == n)]))
})
names(inner) <- names(ls)
inner
})
names(res) <- names(ls[[1]])
res
}
Выполнение str(revert_list_str_1(z))
Я получаю следующий вывод, соответствующий тому, что я хотел.
List of 3
$ a:List of 2
..$ z1: num 1
..$ z2: num 1
$ b:List of 2
..$ z1: num 2
..$ z2: num 4
$ c:List of 2
..$ z1: num 3
..$ z2: num 0
Но, как я сказал , я бы хотел изучить (и узнать) существование более элегантного и динамичного решения.
Фактически мое решение работает полностью, только если все вложенные списки имеют одинаковые имена (также в другом порядке). Это из-за names(ls[[1]])
. Я также хотел бы указать, что он действует только в списках из 2 уровней, как и сообщалось.
Итак, знаете ли вы другие более динамичные решения? Могут ли функции rapply
и/или Filter
быть полезными для этой задачи?
end edit 1.
АНАЛИЗ ПРЕДЛАГАЕМЫХ РЕШЕНИЙ
Я немного проанализировал предлагаемые решения, и все вы! Анализ состоит из проверки следующих точек для всех функций:
- принятые классы (вложенные элементы списка)
- сохраняется и при наличии элементов с разными типами (если они являются атомарными)
- содержащийся в сохраненных элементах (например, в матрице)
- рассмотрены столбцы (для столбцов я имею в виду имена вложенных списков)
- не считаются общие столбцы (классификация "не" понимается положительно в этом случае)
- не сохранены общие столбцы
- он работает также, когда столбцы не совпадают (основанные только на именах первого вложенного списка)
Во всех этих случаях классификация "да" понимается положительно для точки 2.1.
Это все функции, которые я рассмотрел (комментарии относятся к элементам анализа, упомянутым выше):
# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, not 2.3
revert_list_str_1 <- function(ls) { # @leodido
# see above
}
# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_2 <- function(ls) { # @mnel
# convert each component of list to a data.frame
# so rbind.data.frame so named elements are matched
x <- data.frame((do.call(rbind, lapply(ls, data.frame))))
# convert each column into an appropriately named list
o <- lapply(as.list(x), function(i, nam) as.list(`names<-`(i, nam)), nam = rownames(x))
o
}
# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_3 <- function(ls) { # @mnel
# unique names
nn <- Reduce(unique, lapply(ls, names))
# convert from matrix to list `[` used to ensure correct ordering
as.list(data.frame(do.call(rbind,lapply(ls, `[`, nn))))
}
# yes 1.1
# yes 1.2
# yes 2.1, not 2.2, yes 2.3
revert_list_str_4 <- function(ls) { # @Josh O'Brien
# get sub-elements in same order
x <- lapply(ls, `[`, names(ls[[1]]))
# stack and reslice
apply(do.call(rbind, x), 2, as.list)
}
# not 1.1
# not 1.2
# not 2.1, not 2.2, not 2.3
revert_list_str_5 <- function(ls) { # @mnel
apply(data.frame((do.call(rbind, lapply(ls, data.frame)))), 2, as.list)
}
# not 1.1
# not 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_6 <- function(ls) { # @baptiste + @Josh O'Brien
b <- recast(z, L2 ~ L1)
apply(b, 1, as.list)
}
# yes 1.1
# yes 1.2
# not 2.1, yes 2.2, yes 2.3
revert_list_str_7 <- function(ll) { # @Josh O'Brien
nms <- unique(unlist(lapply(ll, function(X) names(X))))
ll <- lapply(ll, function(X) setNames(X[nms], nms))
ll <- apply(do.call(rbind, ll), 2, as.list)
lapply(ll, function(X) X[!sapply(X, is.null)])
}
ОТХОДОВ
Из этого анализа вытекает, что:
- Функции
revert_list_str_7
иrevert_list_str_6
являются наиболее гибкими относительно имен вложенного списка - функции
revert_list_str_4
,revert_list_str_3
, за которыми следуют мои собственные функции, достаточно полные, хорошие компромиссы. - наиболее полная в абсолютной функции
revert_list_str_7
.
ОРИЕНТИРЫ
Для завершения работы я выполнил несколько небольших тестов (с пакетом microbenchmark
R) для этих 4 функций (times = 1000 для каждого теста).
BENCHMARK 1
Input:
list(z1 = list(a = 1, b = 2, c = 3), z2 = list(a = 0, b = 3, d = 22, f = 9))
.
Результаты:
Unit: microseconds
expr min lq median uq max
1 func_1 250.069 467.5645 503.6420 527.5615 2028.780
2 func_3 204.386 393.7340 414.5485 429.6010 3517.438
3 func_4 89.922 173.7030 189.0545 194.8590 1669.178
4 func_6 11295.463 20985.7525 21433.8680 21934.5105 72476.316
5 func_7 348.585 387.0265 656.7270 691.2060 2393.988
Победитель: revert_list_str_4
.
BENCHMARK 2
Input:
list(z1 = list(a = 1, b = 2, c = 'ciao'), z2 = list(a = 0, b = 3, c = 5))
.
revert_list_str_6
исключен, поскольку он не поддерживает разные типы вложенных дочерних элементов.
Результаты:
Unit: microseconds
expr min lq median uq max
1 func_1 249.558 483.2120 502.0915 550.7215 2096.978
2 func_3 210.899 387.6835 400.7055 447.3785 1980.912
3 func_4 92.420 170.9970 182.0335 192.8645 1857.582
4 func_7 257.772 469.9280 477.8795 487.3705 2035.101
Победитель: revert_list_str_4
.
BENCHMARK 3
Input:
list(z1 = list(a = 1, b = m, c = 'ciao'), z2 = list(a = 0, b = 3, c = m))
.
m
- это матрица 3x3 целых чисел, а revert_list_str_6
снова исключена.
Результаты:
Unit: microseconds
expr min lq median uq max
1 func_1 261.173 484.6345 503.4085 551.6600 2300.750
2 func_3 209.322 393.7235 406.6895 449.7870 2118.252
3 func_4 91.556 174.2685 184.5595 196.2155 1602.983
4 func_7 252.883 474.1735 482.0985 491.9485 2058.306
Победитель: revert_list_str_4
. Опять же!
end edit 2.
Заключение
Прежде всего: благодаря всем замечательным решениям.
На мой взгляд, если вы заранее знаете, что список будет иметь вложенный список с одинаковыми именами, reverse_str_4
является победителем как лучший компромисс между выступлениями и поддержкой для разных типов.
Наиболее полное решение revert_list_str_7
, хотя полная гибкость приводит к тому, что в среднем примерно в 2,5 раза ухудшается производительность по сравнению с reverse_str_4
(полезно, если ваш вложенный список имеет разные имена).