6
votes

Comment lier deux listes avec la même structure?

Introduction

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 :

  • Les éléments d'une liste à un certain niveau sont soit tous nommés, soit aucun n'est nommé;
  • Lorsque nous avons nommé des éléments, il n'y a jamais de noms dupliqués à ce niveau;
  • Les relations entre les nœuds parent-enfant sont les mêmes pour les deux listes, lorsque les nœuds sont eux-mêmes nommés des éléments.

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:

  • Utilisation de la base rapply ;
  • Solution Tidyverse avec une combinaison de fonctions purrr ;
  • Fonctions du package rlist

serait génial!

Exemple

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

2 commentaires

La fonction récursive me vient à l'esprit ... peut-être en savoir plus sur ? Stack


@ zx8754: Merci! Je vais jeter un oeil.


3 Réponses :


3
votes

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.

modifier pour prendre un certain nombre de listes:

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


8 commentaires

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.



2
votes

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"


1 commentaires

C'est une bonne solution!



0
votes

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


0 commentaires