plucking items from a list of vectors

purrr

#1

I'm trying to take a list of vectors and if the vector has 3 or more elements I want to grab element 3. If it does not have 3 elements, I want to grab whichever element is the last one. I can't wrap my head around how to do this. I thought I could use if_else along with pluck but I'm not getting far:

library(tidyverse)
lst <- list("this/that/theother/notthis",
            "this",
            "",
            "this/that")

lst %>%
  str_split(., "/") %>%
  if_else(length(.) >= 3, pluck(3), pluck(length(.)))
#> Error: `condition` must be a logical, not list

In this example I would like the result to return a list with three elements: "theother", "this","","that". I would not be stressed if the vector with no records returned NA, but I do need at least an empty placeholder there.

Thoughts on how I might elegantly do this?


#2

I'm sure there are better ways to do this, but this uses your logic but maps the ifelse() over the list of vectors.

library(tidyverse)
lst <- list("this/that/theother/notthis",
            "this",
            "",
            "this/that")

lst %>%
  str_split(., "/") %>%
  map(~ ifelse(length(.) >= 3, pluck(., 3), pluck(., length(.))))
#> [[1]]
#> [1] "theother"
#> 
#> [[2]]
#> [1] "this"
#> 
#> [[3]]
#> [1] ""
#> 
#> [[4]]
#> [1] "that"

Created on 2019-01-13 by the reprex package (v0.2.1)


#3

To extract out the function to ease readability you can use as_mapper() to create the function:

library(tidyverse)
lst <- list("this/that/theother/notthis",
            "this",
            "",
            "this/that")

pred_func <- as_mapper(~ifelse(length(.x) >= 3, 
                               pluck(.x,3), 
                               pluck(.x, length(.x))))

test <- lst %>%
  str_split(., "/") %>%
  map(pred_func)

Can't think of any other way, keep() might be useful but can't think exactly how.


#4

You could use a regular expression to extract the desired pieces.

library(stringr)

lst <- list(
  "this/that/theother/notthis",
  "this",
  "",
  "this/that"
)

str_match(lst, "(?:[^/]+/){0,2}([^/]+)")[, 2]
# [1] "theother" "this"     NA         "that"   

#5

You can use lengths and pmin to generate the indices, which you can apply with [[ or pluck via map2:

library(tidyverse)

lst <- list("this/that/theother/notthis",
            "this",
            "",
            "this/that") %>% 
    str_split('/')

lst %>% map2_chr(pmin(3, lengths(.)), `[[`)
#> [1] "theother" "this"     ""         "that"

lst %>% map2_chr(pmin(3, lengths(.)), pluck)
#> [1] "theother" "this"     ""         "that"

#6

Lots of great solutions here, but I think @alistaire's is probably the "tidiest" (both code-wise and in terms of the todyverse and purrr philosophies) :slightly_smiling_face:


#7

as_mapper is new to me. Thanks for illustrating that.


#8

Very nice! Thank you. I tend to use pluck instead of [[ only because it saves me a few WTFs later. And I like to conserve my WTFs for when I really need them.