Natural Sort Order in C#

后端 未结 17 2090
野性不改
野性不改 2020-11-21 04:54

Anyone have a good resource or provide a sample of a natural order sort in C# for an FileInfo array? I am implementing the IComparer interface in

相关标签:
17条回答
  • 2020-11-21 05:49

    Expanding on a couple of the previous answers and making use of extension methods, I came up with the following that doesn't have the caveats of potential multiple enumerable enumeration, or performance issues concerned with using multiple regex objects, or calling regex needlessly, that being said, it does use ToList(), which can negate the benefits in larger collections.

    The selector supports generic typing to allow any delegate to be assigned, the elements in the source collection are mutated by the selector, then converted to strings with ToString().

        private static readonly Regex _NaturalOrderExpr = new Regex(@"\d+", RegexOptions.Compiled);
    
        public static IEnumerable<TSource> OrderByNatural<TSource, TKey>(
            this IEnumerable<TSource> source, Func<TSource, TKey> selector)
        {
            int max = 0;
    
            var selection = source.Select(
                o =>
                {
                    var v = selector(o);
                    var s = v != null ? v.ToString() : String.Empty;
    
                    if (!String.IsNullOrWhiteSpace(s))
                    {
                        var mc = _NaturalOrderExpr.Matches(s);
    
                        if (mc.Count > 0)
                        {
                            max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                        }
                    }
    
                    return new
                    {
                        Key = o,
                        Value = s
                    };
                }).ToList();
    
            return
                selection.OrderBy(
                    o =>
                    String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                         .Select(o => o.Key);
        }
    
        public static IEnumerable<TSource> OrderByDescendingNatural<TSource, TKey>(
            this IEnumerable<TSource> source, Func<TSource, TKey> selector)
        {
            int max = 0;
    
            var selection = source.Select(
                o =>
                {
                    var v = selector(o);
                    var s = v != null ? v.ToString() : String.Empty;
    
                    if (!String.IsNullOrWhiteSpace(s))
                    {
                        var mc = _NaturalOrderExpr.Matches(s);
    
                        if (mc.Count > 0)
                        {
                            max = Math.Max(max, mc.Cast<Match>().Max(m => m.Value.Length));
                        }
                    }
    
                    return new
                    {
                        Key = o,
                        Value = s
                    };
                }).ToList();
    
            return
                selection.OrderByDescending(
                    o =>
                    String.IsNullOrWhiteSpace(o.Value) ? o.Value : _NaturalOrderExpr.Replace(o.Value, m => m.Value.PadLeft(max, '0')))
                         .Select(o => o.Key);
        }
    
    0 讨论(0)
  • 2020-11-21 05:50

    I've actually implemented it as an extension method on the StringComparer so that you could do for example:

    • StringComparer.CurrentCulture.WithNaturalSort() or
    • StringComparer.OrdinalIgnoreCase.WithNaturalSort().

    The resulting IComparer<string> can be used in all places like OrderBy, OrderByDescending, ThenBy, ThenByDescending, SortedSet<string>, etc. And you can still easily tweak case sensitivity, culture, etc.

    The implementation is fairly trivial and it should perform quite well even on large sequences.


    I've also published it as a tiny NuGet package, so you can just do:

    Install-Package NaturalSort.Extension
    

    The code including XML documentation comments and suite of tests is available in the NaturalSort.Extension GitHub repository.


    The entire code is this (if you cannot use C# 7 yet, just install the NuGet package):

    public static class StringComparerNaturalSortExtension
    {
        public static IComparer<string> WithNaturalSort(this StringComparer stringComparer) => new NaturalSortComparer(stringComparer);
    
        private class NaturalSortComparer : IComparer<string>
        {
            public NaturalSortComparer(StringComparer stringComparer)
            {
                _stringComparer = stringComparer;
            }
    
            private readonly StringComparer _stringComparer;
            private static readonly Regex NumberSequenceRegex = new Regex(@"(\d+)", RegexOptions.Compiled | RegexOptions.CultureInvariant);
            private static string[] Tokenize(string s) => s == null ? new string[] { } : NumberSequenceRegex.Split(s);
            private static ulong ParseNumberOrZero(string s) => ulong.TryParse(s, NumberStyles.None, CultureInfo.InvariantCulture, out var result) ? result : 0;
    
            public int Compare(string s1, string s2)
            {
                var tokens1 = Tokenize(s1);
                var tokens2 = Tokenize(s2);
    
                var zipCompare = tokens1.Zip(tokens2, TokenCompare).FirstOrDefault(x => x != 0);
                if (zipCompare != 0)
                    return zipCompare;
    
                var lengthCompare = tokens1.Length.CompareTo(tokens2.Length);
                return lengthCompare;
            }
            
            private int TokenCompare(string token1, string token2)
            {
                var number1 = ParseNumberOrZero(token1);
                var number2 = ParseNumberOrZero(token2);
    
                var numberCompare = number1.CompareTo(number2);
                if (numberCompare != 0)
                    return numberCompare;
    
                var stringCompare = _stringComparer.Compare(token1, token2);
                return stringCompare;
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:51

    Pure C# solution for linq orderby:

    http://zootfroot.blogspot.com/2009/09/natural-sort-compare-with-linq-orderby.html

    public class NaturalSortComparer<T> : IComparer<string>, IDisposable
    {
        private bool isAscending;
    
        public NaturalSortComparer(bool inAscendingOrder = true)
        {
            this.isAscending = inAscendingOrder;
        }
    
        #region IComparer<string> Members
    
        public int Compare(string x, string y)
        {
            throw new NotImplementedException();
        }
    
        #endregion
    
        #region IComparer<string> Members
    
        int IComparer<string>.Compare(string x, string y)
        {
            if (x == y)
                return 0;
    
            string[] x1, y1;
    
            if (!table.TryGetValue(x, out x1))
            {
                x1 = Regex.Split(x.Replace(" ", ""), "([0-9]+)");
                table.Add(x, x1);
            }
    
            if (!table.TryGetValue(y, out y1))
            {
                y1 = Regex.Split(y.Replace(" ", ""), "([0-9]+)");
                table.Add(y, y1);
            }
    
            int returnVal;
    
            for (int i = 0; i < x1.Length && i < y1.Length; i++)
            {
                if (x1[i] != y1[i])
                {
                    returnVal = PartCompare(x1[i], y1[i]);
                    return isAscending ? returnVal : -returnVal;
                }
            }
    
            if (y1.Length > x1.Length)
            {
                returnVal = 1;
            }
            else if (x1.Length > y1.Length)
            { 
                returnVal = -1; 
            }
            else
            {
                returnVal = 0;
            }
    
            return isAscending ? returnVal : -returnVal;
        }
    
        private static int PartCompare(string left, string right)
        {
            int x, y;
            if (!int.TryParse(left, out x))
                return left.CompareTo(right);
    
            if (!int.TryParse(right, out y))
                return left.CompareTo(right);
    
            return x.CompareTo(y);
        }
    
        #endregion
    
        private Dictionary<string, string[]> table = new Dictionary<string, string[]>();
    
        public void Dispose()
        {
            table.Clear();
            table = null;
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:53

    Inspired by Michael Parker's solution, here is an IComparer implementation that you can drop in to any of the linq ordering methods:

    private class NaturalStringComparer : IComparer<string>
    {
        public int Compare(string left, string right)
        {
            int max = new[] { left, right }
                .SelectMany(x => Regex.Matches(x, @"\d+").Cast<Match>().Select(y => (int?)y.Value.Length))
                .Max() ?? 0;
    
            var leftPadded = Regex.Replace(left, @"\d+", m => m.Value.PadLeft(max, '0'));
            var rightPadded = Regex.Replace(right, @"\d+", m => m.Value.PadLeft(max, '0'));
    
            return string.Compare(leftPadded, rightPadded);
        }
    }
    
    0 讨论(0)
  • 2020-11-21 05:54

    Let me explain my problem and how i was able to solve it.

    Problem:- Sort files based on FileName from FileInfo objects which are retrieved from a Directory.

    Solution:- I selected the file names from FileInfo and trimed the ".png" part of the file name. Now, just do List.Sort(), which sorts the filenames in Natural sorting order. Based on my testing i found that having .png messes up sorting order. Have a look at the below code

    var imageNameList = new DirectoryInfo(@"C:\Temp\Images").GetFiles("*.png").Select(x =>x.Name.Substring(0, x.Name.Length - 4)).ToList();
    imageNameList.Sort();
    
    0 讨论(0)
提交回复
热议问题