J'ai deux listes imbriquées avec la même structure que je voudrais combiner (dans le sens c ()
).
Il existe peut-être déjà un concept pour ce que j'entends par même structure en théorie des graphes, ou en informatique, pour cette relation mais je n'en suis pas conscient.
Voici donc ma tentative de clarifier ce que je signifie par même structure :
Je me demande donc s'il existe déjà une solution à ce problème qui, selon moi, pourrait être assez générale et courante ... (?) Toute solution impliquant:
rapply
; purrr
; rlist
serait génial!
foo
et bar
sont deux exemples de listes avec la même structure .
wonderful
est la liste souhaitée qui résulte de la combinaison de foo
et bar
(fait manuellement).
J'espère que c'est assez clair!
# Input lists: foo and bar foo <- list(a = list(a1 = 1:3, a2 = rep('a', 3)), b = list(b1 = list(b11 = c(4,5,6), b12 = rep('b', 3)), b2 = list(b21 = list(b31 = c(0, 1, 2)))), c = list(list(c21 = 1:3), list(c21 = 4:6), list(c21 = 7:9))) bar <- list(a = list(a1 = 1:3, a2 = rep('z', 3)), b = list(b1 = list(b11 = c(-1,2,5), b12 = rep('b', 3)), b2 = list(b21 = list(b31 = -c(1,2,3)))), c = list(list(c21 = 3:1), list(c21 = 5:3))) # wonderful: desired list (result from combining foo and bar) wonderful <- list( a = list( a1 = c(foo$a$a1, bar$a$a1), a2 = c(foo$a$a2, bar$a$a2) ), b = list( b1 = list( b11 = c(foo$b$b1$b11, bar$b$b1$b11), b12 = c(foo$b$b1$b12, bar$b$b1$b12) ), b2 = list( b21 = list( b31 = c(foo$b$b2$b21$b31, bar$b$b2$b21$b31) ) ) ), c = c(foo$c, bar$c) ) str(foo) #> List of 3 #> $ a:List of 2 #> ..$ a1: int [1:3] 1 2 3 #> ..$ a2: chr [1:3] "a" "a" "a" #> $ b:List of 2 #> ..$ b1:List of 2 #> .. ..$ b11: num [1:3] 4 5 6 #> .. ..$ b12: chr [1:3] "b" "b" "b" #> ..$ b2:List of 1 #> .. ..$ b21:List of 1 #> .. .. ..$ b31: num [1:3] 0 1 2 #> $ c:List of 3 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 1 2 3 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 4 5 6 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 7 8 9 str(bar) #> List of 3 #> $ a:List of 2 #> ..$ a1: int [1:3] 1 2 3 #> ..$ a2: chr [1:3] "z" "z" "z" #> $ b:List of 2 #> ..$ b1:List of 2 #> .. ..$ b11: num [1:3] -1 2 5 #> .. ..$ b12: chr [1:3] "b" "b" "b" #> ..$ b2:List of 1 #> .. ..$ b21:List of 1 #> .. .. ..$ b31: num [1:3] -1 -2 -3 #> $ c:List of 2 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 3 2 1 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 5 4 3 str(wonderful) #> List of 3 #> $ a:List of 2 #> ..$ a1: int [1:6] 1 2 3 1 2 3 #> ..$ a2: chr [1:6] "a" "a" "a" "z" ... #> $ b:List of 2 #> ..$ b1:List of 2 #> .. ..$ b11: num [1:6] 4 5 6 -1 2 5 #> .. ..$ b12: chr [1:6] "b" "b" "b" "b" ... #> ..$ b2:List of 1 #> .. ..$ b21:List of 1 #> .. .. ..$ b31: num [1:6] 0 1 2 -1 -2 -3 #> $ c:List of 5 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 1 2 3 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 4 5 6 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 7 8 9 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 3 2 1 #> ..$ :List of 1 #> .. ..$ c21: int [1:3] 5 4 3
3 Réponses :
Voici un essai:
prec_map <- function(...){ dots <- list(...) first_el = dots[[1]] if(is.atomic(first_el) | is.null(names(first_el))){ do.call(c, dots) } else { imap(first_el, function(el, nme){ one_level_down <- map(dots, nme) do.call(prec_map, one_level_down) }) } } temp <- prec_map(foo, bar) all.equal(temp, wonderful) [1] TRUE
Je ne suis en aucun cas un informaticien, alors prenez la solution avec des pincettes. Je ne suis pas certain du comportement souhaité lorsqu'il n'y a pas de noms pour un niveau, mais à un niveau inférieur, il y a des noms (par exemple, foo $ c
). J'ai donc juste combiné les résultats ( c ()
) si nous rencontrions un niveau sans noms.
library(purrr) rec_map <- function(fizz, buzz) { if(is.atomic(fizz) | is.null(names(fizz))){ c(fizz, buzz) } else { imap(fizz, ~rec_map(fizz[[.y]], buzz[[.y]])) } } temp <- rec_map(foo, bar) all.equal(temp, wonderful) #> [1] TRUE
Magnifique! Comment tu penses que je pourrais changer rec_map
en prec_map
comme dans map
et pmap
, c'est-à-dire une version parallèle de rec_map
?
@rmagno J'ai l'intention de jeter un coup d'œil à cela plus tard dans la journée quand j'aurai encore du temps, désolé!
prec_map
peut être exprimé comme function (.l) reduction (.l, rec_map)
ou partial (reduction, .f = rec_map)
ou < code>. %>% reduction (rec_map) , utilisé comme prec_map (list (foo, bar))
J'aime la suggestion de @ Aurèle d'utiliser la réduction pour gérer plus de listes. J'ai pris un peu plus une approche descendante ci-dessus (plutôt que côte à côte).
@zack: est le buzz
passé dans imap (fizz, ~ rec_map (fizz [[. y]], buzz [[. y]]), buzz)
faisant quelque chose ?
ça ne lui ressemble pas, je pensais qu'il prenait le buzz de l'argument dots à imap
mais il le tirait des arguments passés à la fonction rec_map
. Je l'ai édité maintenant.
@zack: Sympa, encore plus simple alors. Le |
dans is.atomic (fizz) | is.null (names (fizz))
devrait être ||
, non? (bien que cela ne fasse aucune différence dans ce cas).
soit fonctionne dans ce cas. Ils reviendront au même car is.null
et is.atomic
renvoient des vecteurs logiques de longueur 1. Je suppose que si vous voulez vous assurer que vous n'avez pas accidentellement passé un vecteur> longueur 1 à l'instruction if
, vous utiliseriez |
(il affichera un Cas). Si vous préférez ne pas vous en soucier, ||
fonctionnerait.
list_merge
fait quelque chose qui se rapproche des exigences:
plist_merge <- function(.l) { reduce(.l, ~ list_merge(.x, !!! .y)) } all.equal( plist_merge(list(foo, bar)), list_merge(foo, !!! bar) ) # [1] TRUE
La seule différence semble être pour les éléments qui sont des listes sans nom (par exemple foo $ c et
bar $ c
), dont les éléments sont concaténés par position ( foo $ c [[1]]
avec bar $ c [[1 ]]
, foo $ c [[2]]
avec bar $ c [[2]]
et foo $ c [[3] ]
laissé seul puisqu'il n'y a pas de bar $ c [[3]]
... plutôt que c (foo $ c, bar $ c)
).
Et une version parallèle pourrait être:
library(purrr) res <- list_merge(foo, !!! bar) all.equal(wonderful, list_merge(foo, !!! bar)) # [1] "Component âcâ: Length mismatch: comparison on first 3 components" # [2] "Component âcâ: Component 1: Component 1: Numeric: lengths (3, 6) differ" # [3] "Component âcâ: Component 2: Component 1: Numeric: lengths (3, 6) differ"
C'est une bonne solution!
Après avoir réfléchi un peu plus à ce problème en général ... et après m'être inspiré des jointures de dplyr, voici trois jointures pour des listes pour ma propre référence future:
lst_left_join
lst_right_join
lst_inner_join
library(purrr) # # Inspired by dplyr's joins: https://r4ds.had.co.nz/relational-data.html#inner-join # Here's some (more or less equivalent) list joins # lst_left_join <- function(lst_x, lst_y) { if(is.atomic(lst_x) || is.null(names(lst_x))){ c(lst_x, lst_y) } else { imap(lst_x, ~lst_left_join(lst_x[[.y]], lst_y[[.y]])) } } plst_left_join <- function(.l) reduce(.l, lst_left_join) lst_right_join <- function(lst_x, lst_y) { if(is.atomic(lst_y) || is.null(names(lst_y))){ c(lst_x, lst_y) } else { imap(lst_y, ~lst_right_join(lst_x[[.y]], lst_y[[.y]])) } } plst_right_join <- function(.l) reduce(.l, lst_right_join) lst_inner_join <- function(lst_x, lst_y) { if(is.atomic(lst_y) || is.null(names(lst_y))){ c(lst_x, lst_y) } else { common_names <- intersect(names(lst_x), names(lst_y)) names(common_names) <- common_names # so that map preserves names map(common_names, ~lst_inner_join(lst_x[[.x]], lst_y[[.x]])) } } plst_inner_join <- function(.l) reduce(.l, lst_inner_join) # Input lists: foo and bar. foo <- list(x1 = 1:2, x3 = 30+5:6) bar <- list(x1 = 10+1:2, x2 = 10+3:4) # Output lists: r1, r2 and r3. r1 <- lst_left_join(foo, bar) r2 <- lst_right_join(foo, bar) r3 <- lst_inner_join(foo, bar) str(r1) #> List of 2 #> $ x1: num [1:4] 1 2 11 12 #> $ x3: num [1:2] 35 36 str(r2) #> List of 2 #> $ x1: num [1:4] 1 2 11 12 #> $ x2: num [1:2] 13 14 str(r3) #> List of 1 #> $ x1: num [1:4] 1 2 11 12
La fonction récursive me vient à l'esprit ... peut-être en savoir plus sur
? Stack
@ zx8754: Merci! Je vais jeter un oeil.