stats-hb
stats-hb

Reputation: 988

purrr: Iterating over a named list with map (with a function factory)

I want to iterate over a named list (with map), but somehow what works an a single list doesn't work at scale. What is the problem here and what do I have to change to get it working?

I suspect it has sth. to do with the difference between list[1] and list[[1]] but I'm missing it atm.

library(rlang)
library(tidyverse)

# this works
single_list <- list(one = 1)

create_function <- function(mylist){
  function(){
    x <- names(mylist)
    n <- purrr::flatten_chr(mylist)

    rep(x, n)
  }
}

one <- create_function(single_list)
one()
#> [1] "one"


# this doesn't work
long_list <- list(one = 1,
                  two = 2,
                  three = 3)

fun <- long_list %>% 
  map(create_function)

fun$one()
#> Error: `.x` must be a list (double)

Upvotes: 0

Views: 1883

Answers (2)

alistaire
alistaire

Reputation: 43334

When map iterates, it automatically subsets to the contents of each element, so you're calling flatten_chr on a numeric vector, which throws the error. Dropping the flatten_chr call won't actually fix anything, because the names are not passed by map, so you'll just get NULL when you call the functions.

A good approach is to change the factory function to take two parameters, so you can iterate over both the contents and the names. purrr::imap does this iteration automatically, so you can write

library(purrr)

create_function <- function(n, x){
    function(){
        rep(x, n)
    }
}

list(one = 1,two = 2,three = 3) %>% 
    imap(create_function) %>% 
    map(invoke)    # call each function in list
#> $one
#> [1] "one"
#> 
#> $two
#> [1] "two" "two"
#> 
#> $three
#> [1] "three" "three" "three"

Upvotes: 1

akrun
akrun

Reputation: 887038

The way the function was created, it needs a list input

map(seq_along(long_list), ~ create_function(long_list[.x])())
#[[1]]
#[1] "one"

#[[2]]
#[1] "two" "two"

#[[3]]
#[1] "three" "three" "three"

Upvotes: 0

Related Questions