grados - Escribir el visitante de la carpeta recursiva F#en C#- seq vs IEnumerable
grados centigrados (4)
No hay una forma simple de hacer esto. Podría solucionar esto definiendo un tipo de C # que pueda almacenar un valor o una secuencia de valores, utilizando la notación F # sería:
type EnumerationResult<''a> =
| One of ''a
| Seq of seq<''a>
(traduce esto a C # de la manera que quieras :-))
Ahora, podrías escribir algo como:
static IEnumerable<EnumerationResult<string>> Visitor
(string root, string filter) {
foreach (var file in Directory.GetFiles(root, filter))
yield return EnumerationResult.One(file);
foreach (var subdir in Directory.GetDirectories(root))
yield return EnumerationResult.Seq(Visitor(subdir, filter))
}
}
Para usarlo, debe escribir una función que aplana EnumerationResult, que podría ser un método de extensión en C # con la siguiente firma:
IEnumerable<T> Flatten(this IEnumerable<EnumerationResult<T>> res);
Ahora bien, esta es una parte en la que se vuelve complicado: si implementa esto de forma directa, aún contendría "forach" para iterar sobre los resultados "Seq" anidados. Sin embargo, creo que podría escribir una versión optimizada que no tenga complejidad cuadrática.
Ok ... Supongo que este es un tema para una publicación de blog en lugar de algo que podría describirse completamente aquí :-), pero con suerte, ¡muestra una idea que puedes intentar seguir!
[EDITAR: pero por supuesto, también puedes usar la implementación ingenua de "Aplanar" que usaría "SeleccionarMany" solo para hacer que la sintaxis de tu código de iterador C # sea más agradable]
A menudo uso este ''visitante'' recursivo en F #
let rec visitor dir filter=
seq { yield! Directory.GetFiles(dir, filter)
for subdir in Directory.GetDirectories(dir) do yield! visitor subdir filter}
Recientemente comencé a trabajar en la implementación de algunas funcionalidades F # en C #, y estoy tratando de reproducir esto como IEnumerable, pero estoy teniendo dificultades para llegar más allá de esto:
static IEnumerable<string> Visitor(string root, string filter)
{
foreach (var file in Directory.GetFiles(root, filter))
yield return file;
foreach (var subdir in Directory.GetDirectories(root))
foreach (var file in Visitor(subdir, filter))
yield return file;
}
Lo que no entiendo es por qué tengo que hacer un doble foreach en la versión C # para la recursión, pero no en F # ... ¿El seq {} hace implícitamente un ''concat''?
yield!
realiza una operación de "aplanar", por lo que integra la secuencia que pasaste en la secuencia externa, realizando implícitamente un foreach
sobre cada elemento de la secuencia y yield
en cada uno.
En el caso específico de recuperar todos los archivos en un directorio específico, esta sobrecarga de Directory.GetFiles
funciona mejor:
static IEnumerable<string> Visitor( string root, string filter ) {
return Directory.GetFiles( root, filter, SearchOption.AllDirectories );
}
En el caso general de atravesar un árbol de objetos enumerables, se requiere un ciclo foreach anidado o equivalente (ver también: Todo sobre iteradores ).
Editar: se agregó un ejemplo de una función para aplanar cualquier árbol en una enumeración:
static IEnumerable<T> Flatten<T>( T item, Func<T, IEnumerable<T>> next ) {
yield return item;
foreach( T child in next( item ) )
foreach( T flattenedChild in Flatten( child, next ) )
yield return flattenedChild;
}
Esto se puede usar para seleccionar todos los archivos anidados, como antes:
static IEnumerable<string> Visitor( string root, string filter ) {
return Flatten( root, dir => Directory.GetDirectories( dir ) )
.SelectMany( dir => Directory.GetFiles( dir, filter ) );
}
En C #, utilizo el siguiente código para este tipo de función:
public static IEnumerable<DirectoryInfo> TryGetDirectories(this DirectoryInfo dir) {
return F.Swallow(() => dir.GetDirectories(), () => new DirectoryInfo[] { });
}
public static IEnumerable<DirectoryInfo> DescendantDirs(this DirectoryInfo dir) {
return Enumerable.Repeat(dir, 1).Concat(
from kid in dir.TryGetDirectories()
where (kid.Attributes & FileAttributes.ReparsePoint) == 0
from desc in kid.DescendantDirs()
select desc);
}
Esto soluciona los errores de IO (que inevitablemente suceden, desafortunadamente), y evita bucles infinitos debido a enlaces simbólicos (en particular, se encontrará con esa búsqueda de algunos directorios en Windows 7).