在自然排序的序列上优化Join,GroupBy,Distinct,Min / Max

时间:2011-02-07 23:47:03

标签: c# .net linq-to-objects

LINQ运算符的运算假设输入序列没有排序,这对于一般情况很有用。但是,如果源序列按键值排序,则上述运算符可能更有效。

例如,Join将整个内部序列读入哈希表,然后迭代外部序列。如果对两个序列进行了排序,则可以将Join实现为简单合并,而无需额外的存储和哈希表查找。

是否有一个库具有在预先排序的序列上运行的替代高性能LINQ函数?

4 个答案:

答案 0 :(得分:0)

是的,但不适用于LINQ to Objects。在IQueryable<T>上工作的大多数LINQ提供程序已经对“本机”函数进行了转换,可以很容易地进行这种类型的优化。例如,在使用Entity Framework时,EF提供程序会将其转换为SQL调用,并且DB(希望)会正确地优化它。

LINQ to Objects虽然有点不同。在那里,大多数例程(包括上述所有例程)都设计用于处理未排序的数据,甚至是IEqualityComparer<T>IComparer<T>的不同实现。这意味着“优化”版本不仅适用于一小部分潜在数据,而且仅针对标准查询操作的子集进行优化。

话虽如此,对于那些特定情况,使用围绕标准LINQ操作的自己的包装器来执行此操作相当容易。您需要提前知道有问题的集合是如何排序的,然而,这可能需要您自己的单独接口(或运行时检查,例如Count()上的ICollection优化})。

答案 1 :(得分:0)

正如里德所提到的那样,很难发现序列的排序方式,以确定优化是否有效。您并不是真的想要滚动重复的集合类,或者将自己绑定到特定的实现(如IOrderedEnumerable<T>),以编写LINQ扩展方法的覆盖。

那么,只需添加一些新的运算符或重载,您作为消费者可以保证数据的排序。这些仍然可以是IEnumerable<T>上的扩展方法,除非订购了该集合,否则无法保证成功。

一个示例是OrderedJoin,如果每个序列中的当前项目与键。这是作为首发的签名。您可以在实施整个图书馆时通知我们!

IComparable<TKey>

答案 2 :(得分:0)

我有时间开发Nito.LINQ。它为ISortedEnumerableISortedList提供了您建议的一些优化。我还包括更多有争议的优化(例如,Skip IList,这稍微改变了语义。)

答案 3 :(得分:0)

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

namespace OrderedJoin
{
    public static class EnumerableExtension
    {
        private enum JoinType
        {
            Inner,
            Left,
            Right,
            Full
        }

        private static IEnumerable<TResult> OrderedJoinIterator<T, TResult>(
            this IEnumerable<T> left, IEnumerable<T> right, Func<T, T, TResult> resultSelector, JoinType jt, IComparer<T> comparer)
        {
            if (left == null) throw new ArgumentNullException("left");
            if (right == null) throw new ArgumentNullException("right");
            if (resultSelector == null) throw new ArgumentNullException("resultSelector");

            if (comparer == null)
                comparer = Comparer<T>.Default;

            var l = left.GetEnumerator();
            var r = right.GetEnumerator();

            var lHasData = l.MoveNext();
            var rHasData = r.MoveNext();

            while (lHasData || rHasData)
            {
                if (!lHasData && rHasData)
                {
                    if (jt == JoinType.Inner || jt == JoinType.Left)
                        yield break;
                    yield return resultSelector(default(T), r.Current);
                    rHasData = r.MoveNext();
                    continue;
                }
                if (!rHasData && lHasData)
                {
                    if (jt == JoinType.Inner || jt == JoinType.Right)
                        yield break;
                    yield return resultSelector(l.Current, default(T));
                    lHasData = l.MoveNext();
                    continue;
                }

                var comp = comparer.Compare(l.Current, r.Current);

                if (comp < 0)
                {
                    if (jt == JoinType.Left || jt == JoinType.Full)
                        yield return resultSelector(l.Current, default(T));
                    lHasData = l.MoveNext();
                }
                else if (comp > 0)
                {
                    if (jt == JoinType.Right || jt == JoinType.Full)
                        yield return resultSelector(default(T), r.Current);
                    rHasData = r.MoveNext();
                }
                else
                {
                    yield return resultSelector(l.Current, r.Current);
                    lHasData = l.MoveNext();
                    rHasData = r.MoveNext();
                }
            }
        }

        public static IEnumerable<TResult> OrderedInnerJoin<T, TResult>(
            this IEnumerable<T> left, IEnumerable<T> right, Func<T, T, TResult> resultSelector, IComparer<T> comparer = null)
        {
            return OrderedJoinIterator(left, right, resultSelector, JoinType.Inner, comparer);
        }

        public static IEnumerable<TResult> OrderedFullJoin<T, TResult>(
            this IEnumerable<T> left, IEnumerable<T> right, Func<T, T, TResult> resultSelector, IComparer<T> comparer = null)
        {
            return OrderedJoinIterator(left, right, resultSelector, JoinType.Full, comparer);
        }

        public static IEnumerable<TResult> OrderedLeftJoin<T, TResult>(
            this IEnumerable<T> left, IEnumerable<T> right, Func<T, T, TResult> resultSelector, IComparer<T> comparer = null)
        {
            return OrderedJoinIterator(left, right, resultSelector, JoinType.Left, comparer);
        }

        public static IEnumerable<TResult> OrderedRightJoin<T, TResult>(
            this IEnumerable<T> left, IEnumerable<T> right, Func<T, T, TResult> resultSelector, IComparer<T> comparer = null)
        {
            return OrderedJoinIterator(left, right, resultSelector, JoinType.Right, comparer);
        }
    }

    internal class TestEnum : IEnumerable<int>
    {
        public TestEnum(string name, IList<int> nums)
        {
            Name = name;
            Nums = nums;
        }

        public string Name { get; private set; }
        public IList<int> Nums { get; private set; }

        public IEnumerator<int> GetEnumerator()
        {
            foreach (var item in Nums)
            {
                Console.WriteLine("{0}: {1}", Name, item);
                yield return item;
            }
        }

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

    class Program
    {
        static void Main(string[] args)
        {
            var e1 = new TestEnum("L", new List<int> { 1, 2, 5, 6 });
            var e2 = new TestEnum("R", new List<int> { 1, 3, 4, 6 });

            var print = new Action<IEnumerable<string>>(seq => { foreach (var item in seq) Console.WriteLine("\t" + item); });

            Console.WriteLine("Standard Inner Join:");
            print(e1.Join(e2, i => i, j => j, (i, j) => string.Format("{0} <=> {1}", i, j), EqualityComparer<int>.Default));

            Console.WriteLine("Ordered Inner Join:");
            print(e1.OrderedInnerJoin(e2, (i, j) => string.Format("{0} <=> {1}", i, j)));

            Console.WriteLine("Ordered Full Join:");
            print(e1.OrderedFullJoin(e2, (i, j) => string.Format("{0} <=> {1}", i, j)));

            Console.WriteLine("Ordered Left Join:");
            print(e1.OrderedLeftJoin(e2, (i, j) => string.Format("{0} <=> {1}", i, j)));

            Console.WriteLine("Ordered Right Join:");
            print(e1.OrderedRightJoin(e2, (i, j) => string.Format("{0} <=> {1}", i, j)));

            Console.ReadLine();
        }
    }
}