Có một cách tốt "scala-esque" (tôi đoán ý tôi là có chức năng) để liệt kê đệ quy các tệp trong một thư mục không? Điều gì về việc kết hợp một mẫu cụ thể?
Ví dụ: đệ quy tất cả các tệp khớp "a*.foo"
trong c:\temp
.
Có một cách tốt "scala-esque" (tôi đoán ý tôi là có chức năng) để liệt kê đệ quy các tệp trong một thư mục không? Điều gì về việc kết hợp một mẫu cụ thể?
Ví dụ: đệ quy tất cả các tệp khớp "a*.foo"
trong c:\temp
.
Câu trả lời:
Mã Scala thường sử dụng các lớp Java để xử lý I / O, bao gồm cả việc đọc các thư mục. Vì vậy, bạn phải làm một cái gì đó như:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Bạn có thể thu thập tất cả các tệp và sau đó lọc bằng regex:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Hoặc bạn có thể kết hợp regex vào tìm kiếm đệ quy:
import scala.util.matching.Regex
def recursiveListFiles(f: File, r: Regex): Array[File] = {
val these = f.listFiles
val good = these.filter(f => r.findFirstIn(f.getName).isDefined)
good ++ these.filter(_.isDirectory).flatMap(recursiveListFiles(_,r))
}
listFiles
trả về null
nếu f
không trỏ đến một thư mục hoặc nếu có lỗi IO (ít nhất là theo thông số Java). Thêm kiểm tra null có lẽ là khôn ngoan cho việc sử dụng sản xuất.
f.isDirectory
trả về true nhưng f.listFiles
phải trả về null
. Ví dụ: nếu bạn không có quyền đọc các tệp, bạn sẽ nhận được null
. Thay vì có cả hai séc, tôi chỉ thêm một séc rỗng.
f.listFiles
trả về null khi !f.isDirectory
.
Tôi muốn giải pháp với Luồng vì bạn có thể lặp lại qua hệ thống tệp vô hạn (Luồng là tập hợp được đánh giá lười biếng)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Ví dụ để tìm kiếm
getFileTree(new File("c:\\main_dir")).filter(_.getName.endsWith(".scala")).foreach(println)
def getFileTree(f: File): Stream[File] = f #:: Option(f.listFiles()).toStream.flatten.flatMap(getFileTree)
Kể từ Java 1.7, tất cả bạn nên sử dụng java.nio. Nó cung cấp hiệu suất gần như nguyên bản (java.io rất chậm) và có một số trợ giúp hữu ích
Nhưng Java 1.8 giới thiệu chính xác những gì bạn đang tìm kiếm:
import java.nio.file.{FileSystems, Files}
import scala.collection.JavaConverters._
val dir = FileSystems.getDefault.getPath("/some/path/here")
Files.walk(dir).iterator().asScala.filter(Files.isRegularFile(_)).foreach(println)
Bạn cũng đã yêu cầu đối sánh tệp. Hãy thử java.nio.file.Files.find
và cũngjava.nio.file.Files.newDirectoryStream
Xem tài liệu tại đây: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala là một ngôn ngữ đa mô hình. Một cách tốt "scala-esque" để lặp lại một thư mục là sử dụng lại mã hiện có!
Tôi sẽ xem xét việc sử dụng dấu phẩy-io là một cách hoàn hảo để lặp lại một thư mục. Bạn có thể sử dụng một số chuyển đổi ngầm để thực hiện dễ dàng hơn. Giống
import org.apache.commons.io.filefilter.IOFileFilter
implicit def newIOFileFilter (filter: File=>Boolean) = new IOFileFilter {
def accept (file: File) = filter (file)
def accept (dir: File, name: String) = filter (new java.io.File (dir, name))
}
Tôi thích giải pháp stream của yura, nhưng nó (và những giải pháp khác) tái sinh vào các thư mục ẩn. Chúng ta cũng có thể đơn giản hóa bằng cách sử dụng thực tế là listFiles
trả về null cho một thư mục không phải là thư mục.
def tree(root: File, skipHidden: Boolean = false): Stream[File] =
if (!root.exists || (skipHidden && root.isHidden)) Stream.empty
else root #:: (
root.listFiles match {
case null => Stream.empty
case files => files.toStream.flatMap(tree(_, skipHidden))
})
Bây giờ chúng ta có thể liệt kê các tệp
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
hoặc nhận ra toàn bộ luồng để xử lý sau
tree(new File("dir"), true).toArray
Apache Commons Io's FileUtils nằm trên một dòng và khá dễ đọc:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Chưa ai đề cập https://github.com/pathikrit/better-files
val dir = "src"/"test"
val matches: Iterator[File] = dir.glob("**/*.{java,scala}")
// above code is equivalent to:
dir.listRecursively.filter(f => f.extension ==
Some(".java") || f.extension == Some(".scala"))
Hãy xem scala.tools.nsc.io
Có một số tiện ích rất hữu ích ở đó bao gồm chức năng liệt kê sâu trên lớp Thư mục.
Nếu tôi nhớ không nhầm thì điều này đã được đánh dấu (có thể được đóng góp) bởi từ viết tắt và được coi như một điểm dừng trước khi tôi có được một triển khai mới và hoàn chỉnh hơn trong thư viện tiêu chuẩn.
Và đây là hỗn hợp của giải pháp phát trực tiếp từ @DuncanMcGregor với bộ lọc từ @ Rick-777:
def tree( root: File, descendCheck: File => Boolean = { _ => true } ): Stream[File] = {
require(root != null)
def directoryEntries(f: File) = for {
direntries <- Option(f.list).toStream
d <- direntries
} yield new File(f, d)
val shouldDescend = root.isDirectory && descendCheck(root)
( root.exists, shouldDescend ) match {
case ( false, _) => Stream.Empty
case ( true, true ) => root #:: ( directoryEntries(root) flatMap { tree( _, descendCheck ) } )
case ( true, false) => Stream( root )
}
}
def treeIgnoringHiddenFilesAndDirectories( root: File ) = tree( root, { !_.isHidden } ) filter { !_.isHidden }
Điều này cung cấp cho bạn một Luồng [Tệp] thay vì một Danh sách [Tệp] (có thể rất lớn và rất chậm) trong khi cho phép bạn quyết định loại thư mục nào sẽ đệ quy vào bằng hàm hậu duệ ().
Làm thế nào về
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Cá nhân tôi thích sự tinh tế và đơn giản của giải pháp được đề xuất của @Rex Kerr. Nhưng đây là phiên bản đệ quy đuôi có thể trông như thế nào:
def listFiles(file: File): List[File] = {
@tailrec
def listFiles(files: List[File], result: List[File]): List[File] = files match {
case Nil => result
case head :: tail if head.isDirectory =>
listFiles(Option(head.listFiles).map(_.toList ::: tail).getOrElse(tail), result)
case head :: tail if head.isFile =>
listFiles(tail, head :: result)
}
listFiles(List(file), Nil)
}
Đây là một giải pháp tương tự cho Rex Kerr's, nhưng tích hợp bộ lọc tệp:
import java.io.File
def findFiles(fileFilter: (File) => Boolean = (f) => true)(f: File): List[File] = {
val ss = f.list()
val list = if (ss == null) {
Nil
} else {
ss.toList.sorted
}
val visible = list.filter(_.charAt(0) != '.')
val these = visible.map(new File(f, _))
these.filter(fileFilter) ++ these.filter(_.isDirectory).flatMap(findFiles(fileFilter))
}
Phương thức này trả về một Danh sách [Tệp], tiện lợi hơn một chút so với Mảng [Tệp]. Nó cũng bỏ qua tất cả các thư mục bị ẩn (tức là bắt đầu bằng '.').
Nó được áp dụng một phần bằng cách sử dụng bộ lọc tệp bạn chọn, ví dụ:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Giải pháp đơn giản nhất chỉ dành cho Scala (nếu bạn không ngại yêu cầu thư viện trình biên dịch Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
Mặt khác, giải pháp của @ Renaud là ngắn gọn và ngọt ngào (nếu bạn không ngại sử dụng Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Trong trường hợp dir
là một java.io.File:
new File("path/to/dir")
Có vẻ như không ai đề cập đến scala-io
thư viện từ vườn ươm scala ...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
Hoặc với implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
Hoặc nếu bạn muốn implicit
rõ ràng ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
Tài liệu có sẵn tại đây: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
Câu thần chú này phù hợp với tôi:
def findFiles(dir: File, criterion: (File) => Boolean): Seq[File] = {
if (dir.isFile) Seq()
else {
val (files, dirs) = dir.listFiles.partition(_.isFile)
files.filter(criterion) ++ dirs.toSeq.map(findFiles(_, criterion)).foldLeft(Seq[File]())(_ ++ _)
}
}
Bạn có thể sử dụng đệ quy đuôi cho nó:
object DirectoryTraversal {
import java.io._
def main(args: Array[String]) {
val dir = new File("C:/Windows")
val files = scan(dir)
val out = new PrintWriter(new File("out.txt"))
files foreach { file =>
out.println(file)
}
out.flush()
out.close()
}
def scan(file: File): List[File] = {
@scala.annotation.tailrec
def sc(acc: List[File], files: List[File]): List[File] = {
files match {
case Nil => acc
case x :: xs => {
x.isDirectory match {
case false => sc(x :: acc, xs)
case true => sc(acc, xs ::: x.listFiles.toList)
}
}
}
}
sc(List(), List(file))
}
}
Tại sao bạn lại sử dụng File của Java thay vì AbstractFile của Scala?
Với AbstractFile của Scala, hỗ trợ trình lặp cho phép viết một phiên bản ngắn gọn hơn của giải pháp của James Moore:
import scala.reflect.io.AbstractFile
def tree(root: AbstractFile, descendCheck: AbstractFile => Boolean = {_=>true}): Stream[AbstractFile] =
if (root == null || !root.exists) Stream.empty
else
(root.exists, root.isDirectory && descendCheck(root)) match {
case (false, _) => Stream.empty
case (true, true) => root #:: root.iterator.flatMap { tree(_, descendCheck) }.toStream
case (true, false) => Stream(root)
}