Implementing custom IComparer<> (with example)

一个人想着一个人 提交于 2020-06-22 18:58:23

问题


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);

        }
    }
}

回答1:


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);



回答2:


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



回答3:


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.



来源:https://stackoverflow.com/questions/14707650/implementing-custom-icomparer-with-example

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!