9

I am stuck with a certain task. What I want is a function that, given a directory path, would return a recursive-list as an output.

The output should be of the form myList$dir$subdir$subdir$fullFilePath

So basically I want to represent a directory tree as a certain list. I obtain all the files, get all the subdirectories for each of the file, but I am stuck as to how to throw it all into a list with multiple levels.

Jilber Urbina
  • 58,147
  • 10
  • 114
  • 138
Karolis Koncevičius
  • 9,417
  • 9
  • 56
  • 89

3 Answers3

7

Here is a solution using recursion:

tree.list <- function(file.or.dir) {
    isdir <- file.info(file.or.dir)$isdir
    if (!isdir) {
        out <- file.or.dir
    } else {
        files <- list.files(file.or.dir, full.names   = TRUE,
                                         include.dirs = TRUE)
        out <- lapply(files, tree.list)
        names(out) <- basename(files)
    }
    out
}

I have tested it here on a small directory

test.dir <- tree.list("./test")
test.dir
# $a
# $a$`1.txt`
# [1] "./test/a/1.txt"
# 
# $a$aa
# $a$aa$`2.txt`
# [1] "./test/a/aa/2.txt"
# 
# $b
# $b$`3.txt`
# [1] "./test/b/3.txt"

If this is too slow for your needs, I would consider reading all the files into a single call to list.files with recursive = TRUE then do a bit of parsing.

flodel
  • 87,577
  • 21
  • 185
  • 223
  • 1
    Thanks! this is so elegant it hurts. – Karolis Koncevičius Jan 07 '13 at 06:22
  • It won't be too slow for now. And for the parsing - I would still have no idea how to make a recursive list after parsing. after strsplit I would have something like: list(dir=myDir, subdir1=mySubdir1, subdir2=mySubdir2, file=myFile). For loop would not work with list[["myDir"]][[mySubdir1]][[mySubdir2]][[myFile]] <- file, since I can't specify the wanted number of levels in a loop. Thanks again for this answer. – Karolis Koncevičius Jan 07 '13 at 06:45
2

Here is an ugly hack.

mypath <- 'a/b/c/d'


makelist <- function(filepath, fsep = '/'){

  unlisted <- unlist(strsplit(filepath, fsep))

  nsubs <- length(unlisted)

  mylistcall <- paste(paste(rep('list(', nsubs), unlisted, collapse = '='), 
    '= NULL', paste(rep(')', nsubs), collapse = ''))


  mylist <- eval(parse(text = mylistcall))
  return(mylist)
  }

makelist(mypath)

$a
$a$b
$a$b$c
$a$b$c$d
NULL   

Remembering

fortune(106)

If the answer is parse() you should usually rethink the question.
   -- Thomas Lumley
      R-help (February 2005)

In this case however, I would say I should be rethinking the answer.

mnel
  • 113,303
  • 27
  • 265
  • 254
1

Here's a shorter variant of @flodel's wonderful solution using the purrr package:

library( purrr )
tree_list <- function( file_or_dir ) {
  f <- partial(list.files, full.names=TRUE, include.dirs=TRUE) %>%
         compose(tree_list, .)
  file_or_dir %>% set_names( basename(.) ) %>% map_if(dir.exists, f)
}

The first line defines a function f that expands its argument using list.files( ..., full.names=TRUE, include.dirs=TRUE) then applies tree_list() to the expansion.

The second line applies the defined function f to all directories in the original argument.

Artem Sokolov
  • 13,196
  • 4
  • 43
  • 74