开发者

Sort a list of path in LINQ?

开发者 https://www.devze.com 2023-02-25 05:12 出处:网络
Let say I have the following folders: New Folder - New Folder - New Folder (2) - New Folder (3) - New Folder (4)

Let say I have the following folders:

New Folder
- New Folder
- New Folder (2)
- New Folder (3)
- New Folder (4)
New Fold开发者_JAVA技巧er (2)
New Folder (3)
New Folder (4)

And a query

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

The results:

D:\Project\uploads\New Folder
D:\Project\uploads\New Folder (2)
D:\Project\uploads\New Folder (3)
D:\Project\uploads\New Folder (4)
D:\Project\uploads\New Folder\New Folder
D:\Project\uploads\New Folder\New Folder (2)
D:\Project\uploads\New Folder\New Folder (3)
D:\Project\uploads\New Folder\New Folder (4)

Is there anyway to sort the list to the right order? I expected it to be:

D:\Project\uploads\New Folder
D:\Project\uploads\New Folder\New Folder
D:\Project\uploads\New Folder\New Folder (2)
D:\Project\uploads\New Folder\New Folder (3)
D:\Project\uploads\New Folder\New Folder (4)
D:\Project\uploads\New Folder (2)
D:\Project\uploads\New Folder (3)
D:\Project\uploads\New Folder (4)

Any helps would be appreciated!


This wasn't as trivial as I thought. Probably the most sane solution (aside from building the list recursively) is to implement a comparer for this to do the sorting.

class DirectorySorter : IComparer<string>
{
    public int Compare(string x, string y)
    {
        return StringComparer.Ordinal.Compare(x.Replace(Path.DirectorySeparatorChar, '\0'),
                                        y.Replace(Path.DirectorySeparatorChar, '\0'));
        var xPaths = x.Split(Path.DirectorySeparatorChar);
        var yPaths = y.Split(Path.DirectorySeparatorChar);
        var minLength = Math.Min(xPaths.Length, yPaths.Length);
        for (int i = 0; i < minLength; i++)
        {
            var ires = xPaths[i].CompareTo(yPaths[i]);
            if (ires != 0) return ires;
        }
        var lres = xPaths.Length.CompareTo(yPaths.Length);
        if (lres == 0)
        {
            return lres;
        }
        else if (lres < 0)
        {
            var i = y.LastIndexOf(Path.DirectorySeparatorChar);
            return x.Length == i ? lres : -lres;
        }
        else //if (lres > 0)
        {
            var i = x.LastIndexOf(Path.DirectorySeparatorChar);
            return y.Length == i ? lres : -lres;
        }
    }
}

(Seeing Steck's answer shows that I was nearly there with what I originally had. Just that I needed to use the Ordinal string comparer. So it turns out it works using that change.)

On the other hand, we could use some properties of the directory structure to simplify this task and not implement a comparer.

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


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

and then

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


The only thing you need to change about the default ordering is to make sure that the \ character is always treated as the first letter in your alphabet. I don't have an exact answer how to implement this, but:

  • You can use order by clause if you find a way to replace \ in the string with a character that is smaller than all other characters and use this replaced string as the key.

  • You can use Array.Sort and implement your string comparer that re-implements string comparison, but encodes this additional rule about the \ character.


With .NET 4.0 try

 Directory.EnumerateDirectories(@"D:\Project\uploads", "*.*", SearchOption.AllDirectories) 

it might do what you expect. If it doesn't, you can do it explicitly:

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

Lastly you might do something like:

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

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

to get even more control/flexibility. Chose the simplest approach depending on your needs


Thanks for ur comment and answer guys,

I think life'll be much easier with recursive

void Main()
{
    string rootFolder = @"D:\Project\uploads";

    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();
}
0

精彩评论

暂无评论...
验证码 换一张
取 消