Фильтрация рекурсивных каталогов, отбрасывая подпапки


Мне нужна функция, которая будет принимать список папок и удалить все вложенные папки, так что только папки верхнего уровня остались в списке.

Например, приведенный список:

c:\stuf\morestuff
c:\stuf\morestuff\sub1
c:\otherstuf
c:\otherstuf\sub1
c:\otherstuf\sub2

Я хотел, чтобы список будет сокращен до:

c:\stuf\morestuff
c:\otherstuf

Поэтому я придумал такое решение:

// remove empty strings and sub folders
private static void CleanUpFolders(List<string> uniqueFolders)
{
    uniqueFolders.RemoveAll(
        delegate(string curFolder)
        {
            // remove empty
            if (string.IsNullOrEmpty(curFolder))
                return true;

            // remove sub paths
            if (uniqueFolders.Exists(
                delegate(string s)
                {
                    if (!string.IsNullOrEmpty(s) &&
                        curFolder.StartsWith(s) &&
                        string.Compare(s, curFolder) != 0)
                        return true;
                    return false;
                } ))
                return true;

            return false;
        }               
    );
}

Это, кажется, работает (хотя и не очень хорошо протестирован) но я сидел и размышлял о некоторых вещах:

  • есть проблема с использованием переменных внутри анонимного метода, которые были объявлены вне?
  • любые потенциальные проблемы со вложенными анонимными методами?
  • какие-либо другие вопросы и лучшие практики стоит упомянуть?


2295
11
задан 10 марта 2011 в 05:03 Источник Поделиться
Комментарии
4 ответа


есть проблема с использованием переменных внутри анонимного метода, которые были объявлены вне?

Нет, это то, что анонимные методы предназначены для! Это очень полезный трюк, чтобы держать в рукаве. Читайте на закрытие. Есть все виды вещей вы можете сделать с ними.

Очевидно, есть проблемы с что-нибудь делаете, когда вы не все понимаю, но то, как вы используете их в ваш код, что это все о!


любые потенциальные проблемы со вложенными анонимными методами?

То же самое.


какие-либо другие вопросы и лучшие практики стоит упомянуть?

Если вы до сих пор используете C# 2, синтаксис упрощен в использовании, что называется лямбда. Вместо использования

delegate(string curFolder) { ..code.. }

вы можете просто пойти :

curFolder => ..code..

Как метод removeall принимает предикат, вы также можете проиграть возвращение ключевое слово. Пока оператор возвращает значение true или false, он будет воспринимать это как возвращение.

У вас есть некоторый код, который в основном происходит :

if x == true
return true
return false

Это может быть упрощено до :

return x

С эти две вещи, ваш код может быть упрощен до :

 uniqueFolders.RemoveAll(
curFolder =>

string.IsNullOrEmpty(curFolder) ||
uniqueFolders.Exists( s=>
!string.IsNullOrEmpty(s) &&
curFolder.StartsWith(s) &&
string.Compare(s, curFolder) != 0)

);

Его немного глоток. Возможно, вы захотите вынести новый метод.

uniqueFolders.RemoveAll( curFolder => IsNotRootFolder(uniqueFolders, curFolder ) );

bool IsNotRootFolder ( uniqueFolders, curFolder )
{
return string.IsNullOrEmpty(curFolder) ||
uniqueFolders.Exists( s=>
!string.IsNullOrEmpty(s) &&
curFolder.StartsWith(s) &&
string.Compare(s, curFolder) != 0)
}

12
ответ дан 10 марта 2011 в 10:03 Источник Поделиться

Одно из соображений производительности. Ваш текущий код сканирования списка в рамках метод removeall (Это О(Н)) и для каждого элемента это вызов существует в списке (это тоже за o(n), так его петля весь список). Существует вызов имеет потенциал, чтобы сделать дешевле, по мере приближения к сделано если элементы удаляются, но в худшем случае это выглядит как о(н^2) реализации. Вместо этого, используйте для поиска HashSet - это делает поиск за O(1) и о(н) исполнения.

Кроме того, использование в рамках класса путь, чтобы получить родительской папки, а не как startswith - ваш нынешний кодекс трактует c:\john как родитель c:\johnny\appleseed и удаляет c:\johnny\appleseed из списка.

private static void CleanUpFolders(List<string> uniqueFolders)
{
var folderLookup = new HashSet<string>(uniqueFolders);
uniqueFolders.RemoveAll(x => String.IsNullOrEmpty(x) ||
x.Generate(Path.GetDirectoryName)
.Skip(1) // the original
.TakeWhile(p => p != Path.GetPathRoot(p))
.Any(folderLookup.Contains));
}

предполагая, что в общем многоразовые создать метод расширения

public static class TExtensions
{
public static IEnumerable<T> Generate<T>(this T initial, Func<T, T> next)
{
var current = initial;
while (true)
{
yield return current;
current = next(current);
}
}
}

тест:

public void Should_only_keep_parent_directories()
{
var directories = new List<string>
{
null,
"",
@"c:\bob",
@"c:\john",
@"c:\johnny\appleseed",
@"c:\bob\mike\nick",
@"C:\a\c",
@"c:\stuf\morestuff",
@"c:\stuf\morestuff\sub1",
@"c:\otherstuf",
@"c:\otherstuf\sub1",
@"c:\otherstuf\sub1a",
@"c:\otherstuf\sub2"
};

CleanUpFolders(directories);
directories.Count.ShouldBeEqualTo(6);
directories.ShouldContainAll(new[]
{
@"c:\bob",
@"c:\stuf\morestuff",
@"c:\otherstuf",
@"C:\a\c",
@"c:\john",
@"c:\johnny\appleseed"
});
}

5
ответ дан 10 марта 2011 в 06:03 Источник Поделиться

is there an issue with using variables inside anonymous methods that were declared outside?

нет, если анонимный метод получает доступ к локальной переменной, то C# будет создать отдельный класс для проведения метода и переменной позволяя ему все, чтобы "просто работать". (*)

any potential issues with nested anonymous methods?

Единственная проблема, которую я вижу с вложенными анонимными методами заключается в том, что это делает код уродливым и трудно читать, но потом я нашел LINQ в целом этот эффект :).

any other issues or best practices worth mentioning?

Поскольку ваши пути являются Windows, вы можете рассмотреть возможность использования без учета регистра сравнения.

Это как я бы сделал это, на основании того, что если отсортировать список, то все строки, начинающиеся со строки на позиции X будет в один блок, который появляется сразу после него.

private static void CleanUpFolders(List<string> uniqueFolders)
{
uniqueFolders.RemoveAll(string.IsNullOrEmpty);
uniqueFolders.Sort(StringComparer.OrdinalIgnoreCase);

int write = 0;
string last = null;

for (int read = 0; read < uniqueFolders.Count; read++)
{
string value = uniqueFolders[read];

if (last = null || value.StartsWith(last, StringComparison.OrdinalIgnoreCase))
{
uniqueFolders[write] = value;
last = value;
write++;
}
}

if (write < uniqueFolders.Count)
{
uniqueFolders.RemoveRange(write, uniqueFolders.Count - write);
}
}


(*) при изменении ссылочной переменной после создания делегата, это повлияет на то, что делегат видит, которые могут быть не очевидны, особенно если вы создаете делегат в петлю, которая получает индекс или текущего элемента.

В конце цикла в этом примере, все делегаты вернутся 10;

Func<int>[] bob = new Func<int>[10];

for(int i = 0; i < bob.Length; i++)
{
bob[i] = () => i;
}

Но в конце этого цикла каждый будет возвращать собственный индекс.

for(int i = 0; i < bob.Length; i++)
{
int j = i;
bob[i] = () => j;
}

3
ответ дан 10 марта 2011 в 09:03 Источник Поделиться

Другие ответы уже почти полный, но мне интересно об использовании вашего кода.

Это настоящие папки, системы выполнения кода? Вы должны получить папки в виде списка строк?

Брайан Райхле уже упоминал, что строковое представление папки зависит от операционной системы. Возможно, вам лучше написать решение на основе Directoryinfoкласс класс.

Это простое рекурсивное решение, используя метод расширения:

public static List<DirectoryInfo> GetTopFolders( this DirectoryInfo dir )
{
List<DirectoryInfo> result = new List<DirectoryInfo>();

DirectoryInfo[] subDirs = dir.GetDirectories();
if ( subDirs.Length > 0 )
{
result.Add( dir );
subDirs.ForEach( d => result.AddRange( GetTopFolders( d ) ) );
}

return result;
}

Он использует следующий метод IEnumerable метод расширения:

public static void ForEach<T>( this IEnumerable<T> source, Action<T> action )
{
foreach ( var item in source )
{
action( item );
}
}

Обновление:

Или даже лучше с помощью LINQ и EnumerateDirectories функции. Обратите внимание, что прошло реж аргумент не входит в этот результат.

public static IEnumerable<DirectoryInfo> GetTopFolders( this DirectoryInfo dir )
{
return from d in dir.EnumerateDirectories("*", SearchOption.AllDirectories)
where d.EnumerateDirectories().Any()
select d;
}

2
ответ дан 10 марта 2011 в 02:03 Источник Поделиться