Отсортировать список путей в LINQ?

#linq #sorting #path #hierarchy #flatten

#linq #сортировка #путь #иерархия #сгладить

Вопрос:

Допустим, у меня есть следующие папки:

 New Folder
- New Folder
- New Folder (2)
- New Folder (3)
- New Folder (4)
New Folder (2)
New Folder (3)
New Folder (4)
  

И запрос

 from s in Directory.GetDirectories(@"D:Projectuploads", "*.*", SearchOption.AllDirectories)
select s
  

Результаты:

 D:ProjectuploadsNew Folder
D:ProjectuploadsNew Folder (2)
D:ProjectuploadsNew Folder (3)
D:ProjectuploadsNew Folder (4)
D:ProjectuploadsNew FolderNew Folder
D:ProjectuploadsNew FolderNew Folder (2)
D:ProjectuploadsNew FolderNew Folder (3)
D:ProjectuploadsNew FolderNew Folder (4)
  

Есть ли способ отсортировать список в правильном порядке? Я ожидал, что это будет:

 D:ProjectuploadsNew Folder
D:ProjectuploadsNew FolderNew Folder
D:ProjectuploadsNew FolderNew Folder (2)
D:ProjectuploadsNew FolderNew Folder (3)
D:ProjectuploadsNew FolderNew Folder (4)
D:ProjectuploadsNew Folder (2)
D:ProjectuploadsNew Folder (3)
D:ProjectuploadsNew Folder (4)
  

Будем признательны за любую помощь!

Ответ №1:

Это было не так тривиально, как я думал. Вероятно, наиболее разумным решением (помимо рекурсивного построения списка) является реализация средства сравнения для выполнения сортировки.

сортировщик каталогов классов: IComparer<строка>
{
 сравнение открытых значений (строка x, строка y)
 { 
 возвращает StringComparer.Порядковый номер.Сравнить(x.Заменить(Path.DirectorySeparatorChar, ''), 
 y.Заменить(Path.DirectorySeparatorChar, ''));
 var XPaths = x.Split(Path.DirectorySeparatorChar);
 var yPaths = y.Split(Path.DirectorySeparatorChar);
 var minLength = Math.Min(XPaths.Длина, yPaths.Длина);
 for (int i = 0; i < Минимальная длина; i   )
 { 
 переменные = XPaths[i].Сравнение(yPaths[i]);
 if (ires != 0) возвращает ires;
 }
 переменные lres = XPaths.Длина.Сравнить (yPaths.Длина);
 если (lres == 0)
 {
 возвращает lres;
 } 
 иначе, если (lres < 0)
 {
 var i = y.lastIndexOf(Path.DirectorySeparatorChar);
 вернуть x.Длина == i ? lres: -lres;
 } 
 else //if (lres > 0)
 {
 var i = x.lastIndexOf(Path.DirectorySeparatorChar);
 возвращает y.Длина == i ? lres: -lres;
 }
 }
}


(Просмотр ответа Стека показывает, что я был почти там с тем, что у меня было изначально. Просто мне нужно было использовать средство сравнения порядковых строк. Итак, оказывается, что это работает с использованием этого изменения.)

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

 var query = Directory
    .EnumerateDirectories(@"D:Projectuploads", "*", SearchOption.AllDirectories)
    .OrderBy(name => name.Replace(Path.DirectorySeparatorChar, ''), StringComparer.Ordinal);
  

Комментарии:

1. @NVA: Да, я только что заметил это… не тот порядок, который я ожидал для некоторых входных данных. Позвольте мне посмотреть, смогу ли я это настроить.

2. @Byul: Я выяснил, как реализовать такой компаратор. Итак, если вы хотите сделать это путем сортировки, вы можете использовать этот компаратор. p.s., изменить имя? 🙂

Ответ №2:

 private class Comparer : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return StringComparer.Ordinal.Compare(x.Replace(Path.DirectorySeparatorChar, ''),
                                                y.Replace(Path.DirectorySeparatorChar, ''));
    }
}
  

и затем

 var source = Directory.GetDirectories(@"D:Projectuploads", "*.*", SearchOption.AllDirectories)
var target = source.OrderBy(x => x, new Comparer()).ToArray();
  

Комментарии:

1. О боже, я понятия не имел, что использование порядкового средства сравнения имеет значение, хотя создание нового средства сравнения здесь на самом деле не обязательно.

2. да. вы правы. x => x.Replace(Path.DirectorySeparatorChar, ») — хороший селектор ключей с помощью StringComparer. Средство сравнения порядковых номеров

Ответ №3:

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

  • Вы можете использовать order by предложение, если найдете способ заменить в строке символ, который меньше всех других символов, и использовать эту замененную строку в качестве ключа.

  • Вы можете использовать Array.Sort и реализовать свой инструмент сравнения строк, который повторно реализует сравнение строк, но кодирует это дополнительное правило о символе.

Комментарии:

1. Вы также можете взломать свой locale cultureinfo для этой цели. Я бы рекомендовал «говорить, что вы имеете в виду» (преднамеренное программирование)

Ответ №4:

С .NET 4.0 попробуйте

  Directory.EnumerateDirectories(@"D:Projectuploads", "*.*", SearchOption.AllDirectories) 
  

это может сделать то, что вы ожидаете. Если этого не происходит, вы можете сделать это явно:

  Directory.GetDirectories(@"D:Projectuploads")
      .SelectMany(dir => dir.GetDirectories().OrderBy(sub => sub.Name))
  

Наконец, вы могли бы сделать что-то вроде:

  from s in Directory.GetDirectories(@"D:Projectuploads", "*.*", SearchOption.AllDirectories)
 order by s.Parent.Name, s.Name
 select s

 from s in Directory.GetDirectories(@"D:Projectuploads", "*.*", SearchOption.AllDirectories)
 let members = s.Name.Split(new [] {Path.SeparatorChar})
 order by members[2], s.Name
 select s
  

чтобы получить еще больше контроля / гибкости. Выбрал самый простой подход в зависимости от ваших потребностей

Комментарии:

1. Хм, об этом не подумал. GetDirectories() Гарантирует ли возврат каталогов в отсортированном порядке? В противном случае вам пришлось бы сортировать его.

2. Я думаю, что вторая версия должна быть рекурсивной.

3. @Tomas: Я могу «тоже так думать», но это нигде не указано явно, позволяя OP решать

Ответ №5:

Спасибо за ваш комментарий и ответ, ребята,

Я думаю, что жизнь будет намного проще с рекурсивным

 void Main()
{
    string rootFolder = @"D:Projectuploads";

    string[] f = Directory.GetDirectories(rootFolder, "*.*", SearchOption.AllDirectories);

    Func<string, string[]> build = null;

    build = (p) => {
        return (from x in f where Path.GetDirectoryName(x) == p
                from y in new string[]{ x }.Union(build(x)) select y).ToArray();
    };

    f = build(rootFolder).Dump();
}