Apakah ada cara "scala-esque" (maksud saya fungsional) yang baik untuk mendaftar file secara rekursif dalam direktori? Bagaimana dengan mencocokkan pola tertentu?
Misalnya secara rekursif semua file yang cocok "a*.foo"
dengan c:\temp
.
Apakah ada cara "scala-esque" (maksud saya fungsional) yang baik untuk mendaftar file secara rekursif dalam direktori? Bagaimana dengan mencocokkan pola tertentu?
Misalnya secara rekursif semua file yang cocok "a*.foo"
dengan c:\temp
.
Jawaban:
Kode scala biasanya menggunakan kelas Java untuk menangani I / O, termasuk membaca direktori. Jadi, Anda harus melakukan sesuatu seperti:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Anda dapat mengumpulkan semua file dan kemudian memfilter menggunakan regex:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Atau Anda bisa memasukkan regex ke dalam pencarian rekursif:
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
mengembalikan null
jika f
tidak mengarah ke direktori atau jika ada kesalahan IO (setidaknya sesuai dengan spesifikasi Java). Menambahkan cek nol mungkin bijaksana untuk penggunaan produksi.
f.isDirectory
mengembalikan true tetapi f.listFiles
mengembalikan null
. Misalnya, jika Anda tidak memiliki izin untuk membaca file, Anda akan mendapatkan file null
. Daripada memiliki kedua cek, saya hanya menambahkan satu cek nol.
f.listFiles
mengembalikan null saat !f.isDirectory
.
Saya lebih suka solusi dengan Streams karena Anda dapat melakukan iterasi pada sistem file tak terbatas (Stream adalah koleksi yang dievaluasi malas)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Contoh pencarian
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)
Mulai Java 1.7, Anda semua harus menggunakan java.nio. Ia menawarkan kinerja yang mendekati asli (java.io sangat lambat) dan memiliki beberapa pembantu yang berguna
Tapi Java 1.8 memperkenalkan dengan tepat apa yang Anda cari:
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)
Anda juga meminta pencocokan file. Mencobajava.nio.file.Files.find
dan jugajava.nio.file.Files.newDirectoryStream
Lihat dokumentasi di sini: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala adalah bahasa multi-paradigma. Cara "scala-esque" yang baik untuk mengiterasi direktori adalah dengan menggunakan kembali kode yang ada!
Saya akan mempertimbangkan untuk menggunakan commons-io sebagai cara yang sangat scala-esque untuk mengulang direktori. Anda dapat menggunakan beberapa konversi implisit untuk membuatnya lebih mudah. Suka
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))
}
Saya suka solusi streaming yura, tetapi (dan yang lainnya) muncul kembali ke direktori tersembunyi. Kita juga dapat menyederhanakan dengan memanfaatkan fakta yang listFiles
mengembalikan null untuk non-direktori.
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))
})
Sekarang kita dapat membuat daftar file
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
atau mewujudkan seluruh aliran untuk diproses nanti
tree(new File("dir"), true).toArray
FileUtils Apache Commons Io cocok dalam satu baris, dan cukup mudah dibaca:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Belum ada yang menyebutkan 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"))
Kunjungi scala.tools.nsc.io
Ada beberapa utilitas yang sangat berguna di sana termasuk fungsionalitas daftar mendalam di kelas Direktori.
Jika saya ingat dengan benar, ini disorot (mungkin dikontribusikan) oleh retronim dan dilihat sebagai sementara sebelum io mendapatkan implementasi yang segar dan lebih lengkap di perpustakaan standar.
Dan inilah campuran solusi streaming dari @DuncanMcGregor dengan filter dari @ 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 }
Ini memberi Anda Stream [File], bukan Daftar [File] (berpotensi besar dan sangat lambat) sambil membiarkan Anda memutuskan jenis direktori mana yang akan digunakan kembali dengan fungsi descendCheck ().
Bagaimana tentang
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Saya pribadi menyukai keanggunan dan kesederhanaan solusi yang diusulkan @Rex Kerr. Tapi inilah versi rekursif ekornya:
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)
}
Berikut solusi yang mirip dengan Rex Kerr, tetapi menggabungkan filter file:
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))
}
Metode ini mengembalikan Daftar [File], yang sedikit lebih nyaman daripada Array [File]. Ini juga mengabaikan semua direktori yang tersembunyi (yaitu, dimulai dengan '.').
Ini diterapkan sebagian menggunakan filter file pilihan Anda, misalnya:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Solusi paling sederhana Scala-only (jika Anda tidak keberatan memerlukan pustaka kompilator Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
Jika tidak, solusi @ Renaud singkat dan manis (jika Anda tidak keberatan menggunakan FileUtils Apache Commons):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Di mana dir
file java.io.:
new File("path/to/dir")
Sepertinya tidak ada yang menyebutkan scala-io
perpustakaan dari scala-inkubrator ...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
Atau dengan implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
Atau jika Anda ingin implicit
secara eksplisit ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
Dokumentasi tersedia di sini: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
Mantra ini bekerja untuk saya:
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]())(_ ++ _)
}
}
Anda dapat menggunakan rekursi ekor untuk itu:
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))
}
}
Mengapa Anda menggunakan File Java sebagai ganti AbstractFile dari Scala?
Dengan AbstractFile Scala, dukungan iterator memungkinkan penulisan versi solusi James Moore yang lebih ringkas:
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)
}