自定义 Lambda 排序扩展

Custom Lambda sort extension

我想使用我的自定义扩展方法来订购对象列表。它只是一个样本,所以它使用了冒泡排序。我目前的状态:

public static IOrderedQueryable<TSource> OrderByBubbleSort<TSource, TKey>(this IQueryable<TSource> source, Func<TSource, TKey> keySelector)
{
    IComparer<TSource> comparer = Comparer<TSource>.Default;
    List<TSource> Result = source.ToList();
    for (int i = (source.Count() - 1); i >= 0; i--)
    {
        for (int j = 1; j <= i; j++)
        {
            if (comparer.Compare(Result[j - 1], Result[j]) > 0)
            {
                var temp = Result[j - 1];
                Result[j - 1] = Result[j];
                Result[j] = temp;
            }
        }
    }
    return (IOrderedQueryable<TSource>)Result;
}

所以我可以这样称呼它:

List<Person> pList = ...
List<Person> Result = pList.OrderByBubbleSort(x => x.Age).ThenBy(x => x.Name).ToList();

我坚持 keySelector。我需要比较所选 属性 的项目(例如 Age

问题:我的比较器的正确语法是怎样的?

您正在拨打comparer.Compare(Result[j - 1], Result[j])。这是错误的,因为它没有像您所说的那样考虑密钥。

您应该通过调用 Func keySelector 获取密钥,并匹配那个:

IComparer<TKey> comparer = Comparer<TKey>.Default;

var x = keySelector(Result[j - 1]);
var y = keySelector(Result[j]);

if (comparer.Compare(x, y) > 0)
{
    ...
}

现在...像其他人所说的那样正确地执行 OrderBy 很困难...这是我在 15 分钟内编写的实现...

public static class MyOrderedEnumerable
{
    public static IOrderedEnumerable<TSource> MyOrderBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
    {
        if (source == null)
        {
            throw new ArgumentNullException();
        }

        if (keySelector == null)
        {
            throw new ArgumentNullException();
        }

        if (comparer == null)
        {
            comparer = Comparer<TKey>.Default;
        }

        Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(x), keySelector(y));
        return new OrderedEnumerableImpl<TSource>(source, comparer2);
    }

    public static IOrderedEnumerable<TSource> MyOrderByDescending<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IComparer<TKey> comparer = null)
    {
        if (source == null)
        {
            throw new ArgumentNullException();
        }

        if (keySelector == null)
        {
            throw new ArgumentNullException();
        }

        if (comparer == null)
        {
            comparer = Comparer<TKey>.Default;
        }

        Comparison<TSource> comparer2 = (x, y) => comparer.Compare(keySelector(y), keySelector(x));
        return new OrderedEnumerableImpl<TSource>(source, comparer2);
    }

    private class OrderedEnumerableImpl<TSource> : IOrderedEnumerable<TSource>
    {
        private readonly IEnumerable<TSource> Source;
        private readonly Comparison<TSource> Comparer;

        public OrderedEnumerableImpl(IEnumerable<TSource> source, Comparison<TSource> comparer)
        {
            Source = source;
            Comparer = comparer;
        }

        public IOrderedEnumerable<TSource> CreateOrderedEnumerable<TKey>(Func<TSource, TKey> keySelector, IComparer<TKey> comparer, bool descending)
        {
            if (comparer == null)
            {
                comparer = Comparer<TKey>.Default;
            }

            Comparison<TSource> comparer2;

            if (descending)
            {
                comparer2 = (x, y) =>
                {
                    int result = Comparer(x, y);
                    if (result == 0)
                    {
                        result = comparer.Compare(keySelector(y), keySelector(x));
                    }
                    return result;
                };
            }
            else
            {
                comparer2 = (x, y) =>
                {
                    int result = Comparer(x, y);
                    if (result == 0)
                    {
                        result = comparer.Compare(keySelector(x), keySelector(y));
                    }
                    return result;
                };
            }

            return new OrderedEnumerableImpl<TSource>(Source, comparer2);
        }

        public IEnumerator<TSource> GetEnumerator()
        {
            var source = Source.ToArray();

            // ** Here you do the sorting! **
            Array.Sort(source, Comparer);

            for (int i = 0; i < source.Length; i++)
            {
                yield return source[i];
            }
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

看到** Here you do the sorting! **了吗?在那里你必须插入你的排序算法。如您所见,有一个 OrderedEnumerableImpl class 执行实现。重点是 ThenBy 使用 CreateOrderedEnumerable 方法。此方法通过将其比较器链接到 OrderBy 的基本比较器来修改排序算法。 但是这里有一点:您不能真正修改顺序,因为可以:

var myorderedcoll = mycoll.OrderBy(x => x.Foo);
var mysubordered1 = myorderedcoll.ThenBy(x => x.Bar1);
var mysubordered2 = myorderedcoll.ThenBy(x => x.Bar2);

显然 mysubordered1Foo+Bar1 排序,而 mysubordered2Foo+Bar2 排序!

所以CreateOrderedEnumerable创建一个新的OrderedEnumerableImplclass,通过获取已经存在的比较器并添加新的比较器(参见CreateOrderedEnumerable的最后一行)

请注意,您将此 class 用作:

var myorderedcoll = mycoll.MyOrderBy(x => x.Foo).ThenBy(x => x.Bar);

等等。

请注意,"real" 实现要复杂得多。如所写,此实现每次进行比较时都会重新计算 key 。实际实现会预先计算所有必要的键以加速比较。

排序,使用wiki:

public IEnumerator<TSource> GetEnumerator()
{
    var source = Source.ToArray();

    // Taken from http://en.wikipedia.org/wiki/Bubble_sort#Pseudocode_implementation

    int n = source.Length;

    do
    {
        int newn = 0;

        for (int i = 1; i <= n - 1; i++)
        {
            if (Comparer(source[i - 1], source[i]) > 0)
            {
                TSource temp = source[i - 1];
                source[i - 1] = source[i];
                source[i] = temp;
                newn = i;
            }
        }

        n = newn;
    }
    while (n != 0);

    for (int i = 0; i < source.Length; i++)
    {
        yield return source[i];
    }
}

这也可以优化:如果我们反转冒泡排序并在数组 "top" 中累积有序元素,那么我们可以为每个循环的冒泡排序 yield return 一个元素。这将缩短拥有第一个元素所需的时间。所以如果你这样做 OrderBy(x => x.Foo).First(),并不是所有的集合都会被排序。