Implementing custom IComparer<> (with example)
Asked Answered
D

3

6

Ive just written the following code, which will order strings by their native string.Compare() but allow a collection of exceptions (in this case customPriority) that will place priority over the default string.Compare() function.

It all seems a bit long winded, I was wondering if there was something built into .NET to allow this?

    var unorderered = new[] { "a", "b", "c", "x", "y", "z" };
    var ordered = unorderered.OrderBy(a => a, new CustomStringComparer());
    //expected order y,x,a,b,c,z

class CustomStringComparer : IComparer<string>
{
    int IComparer<string>.Compare(string x, string y)
    {
        if (x == y)
            return 0;
        else
        {
            //----------------------------
            //beginning of custom ordering
            var customPriority = new[] { "y", "x" };
            if (customPriority.Any(a => a == x) && customPriority.Any(a => a == y)) //both in custom ordered array
            {
                if (Array.IndexOf(customPriority, x) < Array.IndexOf(customPriority, y))
                    return -1;                   
                return 1;
            }
            else if (customPriority.Any(a => a == x)) //only one item in custom ordered array (and its x)                    
                return -1;
            else if (customPriority.Any(a => a == y)) //only one item in custom ordered array (and its y)                    
                return 1;
            //---------------------------
            //degrade to default ordering
            else
                return string.Compare(x, y);

        }
    }
}
Deprave answered 5/2, 2013 at 12:32 Comment(1)
Why are you asking for built-in way? Can't you use third-party libraries?Laurence
L
4

First, I think it's useful to restate the problem: You want to sort by:

  1. the index in the given array; if the item is not in the array, the index is infinity
  2. the string itself

That means you can achieve your sort order by using OrderBy() for the first condition followed by ThenBy() for the second one:

private static uint NegativeToMaxValue(int i)
{
    if (i < 0)
        return uint.MaxValue;
    return (uint)i;
}

…

var ordered = unorderered
    .OrderBy(a => NegativeToMaxValue(Array.IndexOf(new[] { "y", "x" }, a)))
    .ThenBy(a => a);

NegativeToMaxValue() is necessary, because items not in the array should be last, but they would be first normally, because the index is -1. (A hackish and unreadable way to do the same would be to directly cast the result of IndexOf() to uint.)

If you wanted to reuse this sorting by creating an IComparer, I believe there is nothing in .Net to help you with that. But you could use ComparerExtensions instead:

IComparer<string> comparer = KeyComparer<string>
    .OrderBy(a => NegativeToMaxValue(Array.IndexOf(new[] { "y", "x" }, a)))
    .ThenBy(a => a);
Laurence answered 5/2, 2013 at 13:27 Comment(0)
A
1

There is no built-in comparison method to do what you want, but I'm guessing that isn't the "long-winded" part that you are talking about.

What's annoying is that you have to create a custom comparer class just to pass what should be a simple comparison function.

Well, there is a way to mitigate that. You can write a couple of helper classes that allow you to use OrderBy() just by passing the name of a method. If you write these classes, they will work for ALL your OrderBy() statements.

Here's some sample code. The helper classes are called EnumerableExt and ComparisonDelegator. They work together to allow you to pass a method to OrderBy().

The code below is clearly much longer than your code, but remember that the EnumerableExt and ComparisonDelegator classes would be in a separate common assembly, so you shouldn't count those.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Demo
{
    public static class Program
    {
        private static void Main(string[] args)
        {
            var unorderered = new[] { "a", "b", "c", "x", "y", "z" };

            var ordered = unorderered.OrderBy(compare); // Just need to specify the compare method!
        }

        // Each custom compare method must be written specially, as before:

        private static int compare(string x, string y)
        {
            if (x == y)
                return 0;
            else
            {
                //----------------------------
                //beginning of custom ordering
                var customPriority = new[] { "y", "x" };
                if (customPriority.Any(a => a == x) && customPriority.Any(a => a == y)) //both in custom ordered array
                {
                    if (Array.IndexOf(customPriority, x) < Array.IndexOf(customPriority, y))
                        return -1;
                    return 1;
                }
                else if (customPriority.Any(a => a == x)) //only one item in custom ordered array (and its x)                    
                    return -1;
                else if (customPriority.Any(a => a == y)) //only one item in custom ordered array (and its y)                    
                    return 1;
                //---------------------------
                //degrade to default ordering
                else
                    return string.Compare(x, y);

            }
        }
    }

    // The following classes only need to be written once:

    public static class EnumerableExt
    {
        /// <summary>
        /// Convenience method on IEnumerable{T} to allow passing of a
        /// Comparison{T} delegate to the OrderBy method.
        /// </summary>

        public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> list, Comparison<T> comparison)
        {
            Contract.Requires(list != null, "list can't be null.");
            Contract.Requires(comparison != null, "comparer can't be null.");

            return list.OrderBy(t => t, new ComparisonDelegator<T>(comparison));
        }
    }

    /// <summary>
    /// Provides a mechanism for easily converting a Comparison&lt;&gt; delegate (or lambda) to an IComparer&lt;&gt;.
    /// This can be used for List.BinarySearch(), for example.
    /// </summary>
    /// <typeparam name="T">The type of items to be compared.</typeparam>

    public sealed class ComparisonDelegator<T>: IComparer<T>, IComparer
    {
        /// <summary>Create from a Comparison&lt;&gt; delegate.</summary>
        /// <param name="comparison">A Comparison&lt;&gt; delegate.</param>

        public ComparisonDelegator(Comparison<T> comparison)
        {
            Contract.Requires(comparison != null);

            this._comparison = comparison;
        }

        /// <summary>Implements the IComparer.Compare() method.</summary>

        public int Compare(T x, T y)
        {
            return _comparison(x, y);
        }

        /// <summary>Implements the IComparer.Compare() method.</summary>

        public int Compare(object x, object y)
        {
            return _comparison((T)x, (T)y);
        }

        /// <summary>Used to store the Comparison delegate.</summary>

        private readonly Comparison<T> _comparison;
    }
}

You could also then write the compare method inline as follows (but I wouldn't recommend that for such a complex compare method; this is just for the purposes of illustration):

    private static void Main(string[] args)
    {
        var unorderered = new[] { "a", "b", "c", "x", "y", "z" };

        var ordered = unorderered.OrderBy((x, y) =>
        {
            if (x == y)
                return 0;
            else
            {
                var customPriority = new[] { "y", "x" };
                if (customPriority.Any(a => a == x) && customPriority.Any(a => a == y)) //both in custom ordered array
                {
                    if (Array.IndexOf(customPriority, x) < Array.IndexOf(customPriority, y))
                        return -1;
                    return 1;
                }
                else if (customPriority.Any(a => a == x)) //only one item in custom ordered array (and its x)                    
                    return -1;
                else if (customPriority.Any(a => a == y)) //only one item in custom ordered array (and its y)                    
                    return 1;
                else
                    return string.Compare(x, y);
            }
        });
    }
Andrews answered 5/2, 2013 at 12:56 Comment(1)
My guess is that the ordering method is exactly the “long-winded” part. The same logic can expressed on two lines.Laurence
T
0

I am 99.99% sure that, Nothing such exist by default in the .Net Framework.


Your sorting is very much customized, and is not a general way of sorting, hence thing like that do not exist in the .NET Framework by default.

Thyroiditis answered 5/2, 2013 at 12:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.