3

I have an array of objects which all contain string property. I want to sort objects by string property alphabetically in a way that objects with empty string property come at the end of the list. Currently I have this:

switches = switches.OrderBy(n => n.GetCurrentUser()).ToArray();

The problem is that it puts empty strings at the top of the list. How do I put objects with strings with value (sorted alphabetically) at the top and objects with empty strings at the bottom?

Tetsuya Yamamoto
  • 24,297
  • 8
  • 39
  • 61
Sam Carlson
  • 1,891
  • 1
  • 17
  • 44

3 Answers3

3

You can use:

switches = switches
    .Select(n => new { TheObject = n, User = n.GetCurrentUser() })
    .OrderBy(x => String.IsNullOrEmpty(x.User) ? 1 : 0)
    .ThenBy(x => x.User)
    .Select(x => x.TheObject)
    .ToArray();

This will first build two groups, the one with empty user and others. OrderBy will move them to the end because 1 is more than 0. If you want them at the top use OrderByDescending.

Then i use ThenBy to sort alphabetically which will only matter for the non-empty users.

Tim Schmelter
  • 450,073
  • 74
  • 686
  • 939
1

OrderBy has an overload that accepts an IComparer<>T. This allows you to define your own sorting rules. You can start with the generic Comparer class and override the Compare method, eg :

public class EmptyLastComparer: Comparer<string>
{
    public override int Compare(string x, string y)
    {
        if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
        {
            return 1;
        }
        else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
        {
            return 0;
        }
        else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
        {
            return -1;
        }
        else
        {
            return x.CompareTo(y);
        }
    }
}

To use it, creatre a new EmptyLastComparer() instance and pass it to OrderBy :

var myStrings = new[] { "c", "A","a", "A","b", " ","   ",null };
var ordered=myStrings.OrderBy(x => x, new EmptyLastComparer());

String comparison is more complex than just comparing two strings. String.Compare has overloads that allow case-insensitive comparisons, using specific cultures etc. The custom comparer could accepts a StringComparison parameter in its constructor to allow something similar, eg :

public class EmptyLastComparer : Comparer<string>
{
    private readonly StringComparison _comparison;

    public EmptyLastComparer(StringComparison comparison=StringComparison.CurrentCulture)
    {
        _comparison = comparison;
    }
    public override int Compare(string x, string y)
    {
        if (String.IsNullOrWhiteSpace(x) && !String.IsNullOrWhiteSpace(y))
        {
            return 1;
        }
        else if (String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
        {
            return 0;
        }
        else if (!String.IsNullOrWhiteSpace(x) && String.IsNullOrWhiteSpace(y))
        {
            return -1;
        }
        else
        {
            return String.Compare(x,y, _comparison);
        }
    }
}

Perhaps even add some predefined comparers, just like StringComparer does :

    public static EmptyLastComparer CurrentCulture =>
            new EmptyLastComparer();
    public static EmptyLastComparer CurrentCultureIgnoreCase => 
            new EmptyLastComparer(StringComparison.CurrentCultureIgnoreCase);

    public static EmptyLastComparer InvariantCulture =>
            new EmptyLastComparer(StringComparison.InvariantCulture);
    public static EmptyLastComparer InvariantCultureIgnoreCase =>
            new EmptyLastComparer(StringComparison.InvariantCultureIgnoreCase);

    public static EmptyLastComparer Ordinal =>
            new EmptyLastComparer(StringComparison.Ordinal);
    public static EmptyLastComparer OrdinalIgnoreCase =>
            new EmptyLastComparer(StringComparison.OrdinalIgnoreCase);

And use them the same way, without allocating a new comparer each time :

var ordered=myStrings.OrderBy(x => x, EmptyLastComparer.InvariantCultureIgnoreCase);
Panagiotis Kanavos
  • 120,703
  • 13
  • 188
  • 236
1

You can also use inline Comparer creation:

switches.OrderBy(n => n.GetCurrentUser(),                
                Comparer<string>.Create((a, b) =>
                string.IsNullOrEmpty(a) && !string.IsNullOrEmpty(b)? 1 
                : !string.IsNullOrEmpty(a) && string.IsNullOrEmpty(b) ? -1 
                : string.Compare(a, b)));
Access Denied
  • 8,723
  • 4
  • 42
  • 72