1

I am new to C# and Blazor. I'm trying to create a column in a table with a filter pop-up similar to MS Excel's filtered columns. I need a distinct list of values and so I used a SortedSet I called UniqueValues. When they are selected, I am also storing them in a SortedSet called SelectedValues so that I don't need to convert anything.

public readonly SortedSet<string> UniqueValues = new SortedSet<string>(StringComparer.OrdinalIgnoreCase);
public SortedSet<string> SelectedValues { get; set; } = new (StringComparer.OrdinalIgnoreCase);

The problem is with my data that has both numbers & strings in it, the SortedSet of course doesn't sort it in the expected numerical way since it is of type string. To get around a similar issue I was having with OrderBy for an ICollection<T> I made this method:

public static class StringExtensions
{
    public static string NumberPadded(this string text)
    {
        if (string.IsNullOrWhiteSpace(text)) return string.Empty;
        return Regex.Replace(text, "[0-9]+", (Match m) => m.Value.PadLeft(10, '0'));
    }
}

I then called this method where I'm sorting my column data, which looks kind of like public ICollection<T> SortData { return source.OrderBy(x => NumberPadded(x)); } This works, but is there a way to declare a SortedSet as alphanumeric?

I'm trying to keep my UniqueValues and SelectedValues as generic/dynamic as possible so they can be used across multiple data types & I need my data to display sorted and with distinct values only. Would it be better to use a HashSet and the OrderBy function or something? Here is what it looks like currently, as you can see my "Name" column is sorted correctly using the NumberPadded method, but my pop-up isn't

I've looked at Problem with adding sorted number Strings to a SortedSet in Java but I am using C#

H H
  • 263,252
  • 30
  • 330
  • 514
hafootnd
  • 98
  • 9
  • 5
    you can implement your own [Comparer](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.icomparer-1?view=net-6.0) with any comparison logic you want and provide it to the [constructor](https://learn.microsoft.com/en-us/dotnet/api/system.collections.generic.sortedset-1.-ctor?view=net-6.0#system-collections-generic-sortedset-1-ctor(system-collections-generic-icomparer((-0)))) of a `SortedSet` – derpirscher Jun 22 '22 at 18:08
  • Could you please show what that would look like? I have this as an example for Java, but there's no TreeSet in C#: `SortedSet(new Comparator() { public int compare(String number1, String number2) { return Integer.parseInt(number1) - Integer.parseInt(number2); }});` – hafootnd Jun 22 '22 at 19:40
  • You can take that `Comparer` and pass it to the constructor of `SortedSet` – Charlieface Jun 22 '22 at 20:29

1 Answers1

0

Here's an approach based on the example given for SortedSet(IComparer).

Please keep in mind that if you cannot guarantee that your file names are always of the form abcde-123, then additional changes (e.g., exception handling) will need to be made.

using System;
using System.Collections;
using System.Collections.Generic;

public class Test
{
    public static void Main()
    {
        var uniqueNames = new SortedSet<string>(new FileNameComparer());
        var fileNames = new List<string> { "Product-113", "product-7", "product-42" };
        fileNames.ForEach(x => uniqueNames.Add(x));

        foreach (string x in uniqueNames)
        {
            Console.WriteLine(x);
        }

        // Expected output:
        //
        // product-7
        // product-42
        // Product-113
    }

    public class FileNameComparer : IComparer<string>
    {
        private CaseInsensitiveComparer _caseInsensitiveComparer = new CaseInsensitiveComparer();
        private (string Name, int Number) _f1;
        private (string Name, int Number) _f2;

        public int Compare(string fileName1, string fileName2)
        {
            _f1 = ParseFileName(fileName1);
            _f2 = ParseFileName(fileName2);

            int nameResult = _caseInsensitiveComparer.Compare(_f1.Name, _f2.Name);
            return nameResult == 0 ? _f1.Number - _f2.Number : nameResult;
        }

        private (string Name, int Number) ParseFileName(string fileName)
        {
            var parts = fileName.Split('-');
            return (parts[0], int.Parse(parts[1]));
        }
    }
}

Alternatively, the capability that you are asking for is known as Natural sort order. So, instead of rolling your own custom implementation of IComparer<string>, you might consider using NaturalSort.Extension which is available as a NuGet package.

For other possibilities, see Natural Sort Order in C#.

DavidRR
  • 18,291
  • 25
  • 109
  • 191