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
listFilesmethod of theFileclass to list all the files in the given directory as anArray[File]. - The
filtermethod trims that list to contain only directories. mapcallsgetNameon each file to return an array of directory names (instead ofFileinstances).toListconverts 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 |