This is an excerpt from the Scala Cookbook. This is Recipe 12.10, “How to list subdirectories beneath a directory in Scala.”
Problem
You want to generate a list of subdirectories in a given directory.
Solution
Use a combination of the Java File
class and Scala collection methods:
import java.io.File // assumes that dir is a directory known to exist def getListOfSubDirectories(dir: File): List[String] = dir.listFiles .filter(_.isDirectory) .map(_.getName) .toList
This algorithm does the following:
- Uses the
listFiles
method of theFile
class to list all the files in the given directory as anArray[File]
. - The
filter
method trims that list to contain only directories. map
callsgetName
on each file to return an array of directory names (instead ofFile
instances).toList
converts that to aList[String]
.
Calling toList
isn’t necessary, but if it isn’t used, the method should be declared to return Array[String]
.
This method can be used like this:
getListOfSubDirectories(new File("/Users/Al")).foreach(println)
As mentioned, this method returns a List[String]
. If you’d rather return a List[File]
, write the method as follows, dropping the map
method call:
dir.listFiles.filter(_.isDirectory).toList
Discussion
This problem provides a good way to demonstrate the differences between writing code in a functional style versus writing code in an imperative style.
When a developer first comes to Scala from Java, she might write a more Java-like (imperative) version of that method as follows:
def getListOfSubDirectories1(dir: File): List[String] = { val files = dir.listFiles val dirNames = collection.mutable.ArrayBuffer[String]() for (file <- files) { if (file.isDirectory) { dirNames += file.getName } } dirNames.toList }
After getting more comfortable with Scala, she’d realize the code can be shortened. One simplification is that she can eliminate the need for the ArrayBuffer
by using a for
loop with a yield
expression. Because the method should return a List[String]
, the for
loop is made to yield file.getName
, and the for
loop result is assigned to the variable dirs. Finally, dirs
is converted to a List
in the last line of the method, and it’s returned from there:
def getListOfSubDirectories2(dir: File): List[String] = { val files = dir.listFiles val dirs = for { file <- files if file.isDirectory } yield file.getName dirs.toList }
Although there’s nothing wrong with this code — indeed, some programmers prefer writing for-comprehensions to using map — at some point, as the developer gets more comfortable with the Scala collections and FP style, she’ll realize the intention of the code is to create a filtered list of files, and using the filter
method on the collection to return only directories will come to mind. Also, when she sees a for/yield combination, she should think, “map
method,” and in short order, she’ll be at the original solution.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |