Czy istnieje dobry "scala-esque" (chyba mam na myśli funkcjonalny) sposób rekurencyjnego wyświetlania plików w katalogu? A co z dopasowaniem określonego wzorca?
Na przykład rekurencyjnie wszystkie pliki pasujące "a*.foo"do c:\temp.
Czy istnieje dobry "scala-esque" (chyba mam na myśli funkcjonalny) sposób rekurencyjnego wyświetlania plików w katalogu? A co z dopasowaniem określonego wzorca?
Na przykład rekurencyjnie wszystkie pliki pasujące "a*.foo"do c:\temp.
Odpowiedzi:
Kod Scala zazwyczaj używa klas Java do obsługi operacji we / wy, w tym do czytania katalogów. Musisz więc zrobić coś takiego:
import java.io.File
def recursiveListFiles(f: File): Array[File] = {
val these = f.listFiles
these ++ these.filter(_.isDirectory).flatMap(recursiveListFiles)
}
Możesz zebrać wszystkie pliki, a następnie przefiltrować za pomocą wyrażenia regularnego:
myBigFileArray.filter(f => """.*\.html$""".r.findFirstIn(f.getName).isDefined)
Lub możesz włączyć to wyrażenie regularne do wyszukiwania rekurencyjnego:
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))
}
listFileszwraca, nulljeśli fnie wskazuje na katalog lub jeśli wystąpił błąd we / wy (przynajmniej zgodnie ze specyfikacją Java). Dodanie czeku zerowego jest prawdopodobnie rozsądne do użytku produkcyjnego.
f.isDirectoryzwrócenie prawdy, ale f.listFileszwrócenie null. Na przykład, jeśli nie masz uprawnień do odczytu plików, otrzymasz rozszerzenie null. Zamiast mieć oba sprawdzenia, po prostu dodam jeden czek zerowy.
f.listFileszwraca null when !f.isDirectory.
Wolałbym rozwiązanie ze strumieniami, ponieważ można iterować w nieskończonym systemie plików (strumienie są leniwie ocenianymi kolekcjami)
import scala.collection.JavaConversions._
def getFileTree(f: File): Stream[File] =
f #:: (if (f.isDirectory) f.listFiles().toStream.flatMap(getFileTree)
else Stream.empty)
Przykład wyszukiwania
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)
Od wersji Java 1.7 wszyscy powinniście używać java.nio. Oferuje wydajność zbliżoną do natywnej (java.io działa bardzo wolno) i ma kilka przydatnych pomocników
Ale Java 1.8 wprowadza dokładnie to, czego szukasz:
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)
Poprosiłeś również o dopasowanie pliku. Spróbuj java.nio.file.Files.findi teżjava.nio.file.Files.newDirectoryStream
Zobacz dokumentację tutaj: http://docs.oracle.com/javase/tutorial/essential/io/walk.html
for (file <- new File("c:\\").listFiles) { processFile(file) }
Scala jest językiem wieloparadygmatycznym. Dobrym sposobem iteracji katalogu w stylu „scala” byłoby ponowne użycie istniejącego kodu!
Rozważałbym użycie commons-io jako idealnie skalowalnego sposobu iteracji katalogu. Możesz użyć niektórych niejawnych konwersji, aby to ułatwić. Lubić
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))
}
Podoba mi się rozwiązanie strumieniowe Yury, ale (i inne) powraca do ukrytych katalogów. Możemy również uprościć, wykorzystując fakt, że listFileszwraca null dla niekatalogu.
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))
})
Teraz możemy wyświetlić listę plików
tree(new File(".")).filter(f => f.isFile && f.getName.endsWith(".html")).foreach(println)
lub zrealizuj cały strumień do późniejszego przetworzenia
tree(new File("dir"), true).toArray
FileUtils Apache Commons Io mieści się w jednej linii i jest całkiem czytelny:
import scala.collection.JavaConversions._ // important for 'foreach'
import org.apache.commons.io.FileUtils
FileUtils.listFiles(new File("c:\temp"), Array("foo"), true).foreach{ f =>
}
Nikt jeszcze nie wspomniał o 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"))
Spójrz na scala.tools.nsc.io
Jest tam kilka bardzo przydatnych narzędzi, w tym funkcje głębokiego wyświetlania list w klasie Directory.
Jeśli dobrze pamiętam, zostało to podkreślone (prawdopodobnie wniesione) przez retronimę i było postrzegane jako przerwa, zanim io otrzyma świeżą i pełniejszą implementację w standardowej bibliotece.
A oto mieszanina roztworu strumienia z @DuncanMcGregor z filtrem z @ 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 }
Daje to Stream [File] zamiast (potencjalnie ogromnej i bardzo powolnej) List [File], pozwalając jednocześnie zdecydować, które rodzaje katalogów mają się powtarzać za pomocą funkcji descendCheck ().
Co powiesz na
def allFiles(path:File):List[File]=
{
val parts=path.listFiles.toList.partition(_.isDirectory)
parts._2 ::: parts._1.flatMap(allFiles)
}
Osobiście podoba mi się elegancja i prostota rozwiązania proponowanego przez @Rex Kerr. Ale oto jak może wyglądać rekurencyjna wersja ogona:
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)
}
Oto rozwiązanie podobne do Rexa Kerra, ale zawierające filtr plików:
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))
}
Metoda zwraca List [File], co jest nieco wygodniejsze niż Array [File]. Ignoruje również wszystkie katalogi, które są ukryte (tj. Zaczynające się od „.”).
Jest częściowo stosowany przy użyciu wybranego filtru plików, na przykład:
val srcDir = new File( ... )
val htmlFiles = findFiles( _.getName endsWith ".html" )( srcDir )
Najprostsze rozwiązanie tylko dla Scala (jeśli nie masz nic przeciwko wymaganiu biblioteki kompilatora Scala):
val path = scala.reflect.io.Path(dir)
scala.tools.nsc.io.Path.onlyFiles(path.walk).foreach(println)
W przeciwnym razie rozwiązanie @ Renaud jest krótkie i słodkie (jeśli nie masz nic przeciwko ściągnięciu Apache Commons FileUtils):
import scala.collection.JavaConversions._ // enables foreach
import org.apache.commons.io.FileUtils
FileUtils.listFiles(dir, null, true).foreach(println)
Gdzie dirjest plik java.io.:
new File("path/to/dir")
Wygląda na to, że nikt nie wspomina o scala-iobibliotece z inkubatora scala ...
import scalax.file.Path
Path.fromString("c:\temp") ** "a*.foo"
Lub z implicit
import scalax.file.ImplicitConversions.string2path
"c:\temp" ** "a*.foo"
Lub jeśli chcesz implicitwyraźnie ...
import scalax.file.Path
import scalax.file.ImplicitConversions.string2path
val dir: Path = "c:\temp"
dir ** "a*.foo"
Dokumentacja jest dostępna tutaj: http://jesseeichar.github.io/scala-io-doc/0.4.3/index.html#!/file/glob_based_path_sets
To zaklęcie działa dla mnie:
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]())(_ ++ _)
}
}
Możesz użyć do tego rekurencji ogona:
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))
}
}
Dlaczego używasz pliku Java zamiast AbstractFile Scali?
Dzięki AbstractFile Scali obsługa iteratorów umożliwia napisanie bardziej zwięzłej wersji rozwiązania Jamesa Moore'a:
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)
}