在c#中对预先排序的输入进行优化分组

本文关键字:输入 优化 排序 | 更新日期: 2023-09-27 18:14:24

是否有任何内置的LINQ操作符(或。net框架逻辑),将让我利用的知识,输入是预先排序的键在IEnumerable数据源上执行分组时?该实现将有效地允许它绕过分组的缓冲/排序阶段。

IEnumerable<Item> largeDataSet = ... // pre-sorted data
foreach (var grouping in largeDataSet.GroupBy(x => x.PreSorted))
{
    // this should be able to give me the first grouping
    // containing the first element in largeDataSet 
    // almost immediately
}

在c#中对预先排序的输入进行优化分组

这不是"内置的",但微软提供了一个方便的扩展方法来按连续键分组。

如何:按连续键分组

为了子孙后代,这里是他们提供的实现的一字不差的副本:

public static class MyExtensions
{
    public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
    {
        return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);
    }
    public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
    {
        // Flag to signal end of source sequence.
        const bool noMoreSourceElements = true;
        // Auto-generated iterator for the source array.       
        var enumerator = source.GetEnumerator();
        // Move to the first element in the source sequence.
        if (!enumerator.MoveNext()) yield break;
        // Iterate through source sequence and create a copy of each Chunk.
        // On each pass, the iterator advances to the first element of the next "Chunk"
        // in the source sequence. This loop corresponds to the outer foreach loop that
        // executes the query.
        Chunk<TKey, TSource> current = null;
        while (true)
        {
            // Get the key for the current Chunk. The source iterator will churn through
            // the source sequence until it finds an element with a key that doesn't match.
            var key = keySelector(enumerator.Current);
            // Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the current source element.
            current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value)));
            // Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the ChunkBy method.
            // At this point the Chunk only has the first element in its source sequence. The remaining elements will be
            // returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for more info.
            yield return current;
            // Check to see whether (a) the chunk has made a copy of all its source elements or 
            // (b) the iterator has reached the end of the source sequence. If the caller uses an inner
            // foreach loop to iterate the chunk items, and that loop ran to completion,
            // then the Chunk.GetEnumerator method will already have made
            // copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not
            // enumerate all elements in the chunk, we need to do it here to avoid corrupting the iterator
            // for clients that may be calling us on a separate thread.
            if (current.CopyAllChunkElements() == noMoreSourceElements)
            {
                yield break;
            }
        }
    }
    // A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk 
    // has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence.
    class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
    {
        // INVARIANT: DoneCopyingChunk == true || 
        //   (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current)
        // A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each ChunkItem
        // has a reference to the next ChunkItem in the list.
        class ChunkItem
        {
            public ChunkItem(TSource value)
            {
                Value = value;
            }
            public readonly TSource Value;
            public ChunkItem Next = null;
        }
        // The value that is used to determine matching elements
        private readonly TKey key;
        // Stores a reference to the enumerator for the source sequence
        private IEnumerator<TSource> enumerator;
        // A reference to the predicate that is used to compare keys.
        private Func<TSource, bool> predicate;
        // Stores the contents of the first source element that
        // belongs with this chunk.
        private readonly ChunkItem head;
        // End of the list. It is repositioned each time a new
        // ChunkItem is added.
        private ChunkItem tail;
        // Flag to indicate the source iterator has reached the end of the source sequence.
        internal bool isLastSourceElement = false;
        // Private object for thread syncronization
        private object m_Lock;
        // REQUIRES: enumerator != null && predicate != null
        public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
        {
            this.key = key;
            this.enumerator = enumerator;
            this.predicate = predicate;
            // A Chunk always contains at least one element.
            head = new ChunkItem(enumerator.Current);
            // The end and beginning are the same until the list contains > 1 elements.
            tail = head;
            m_Lock = new object();
        }
        // Indicates that all chunk elements have been copied to the list of ChunkItems, 
        // and the source enumerator is either at the end, or else on an element with a new key.
        // the tail of the linked list is set to null in the CopyNextChunkElement method if the
        // key of the next element does not match the current chunk's key, or there are no more elements in the source.
        private bool DoneCopyingChunk { get { return tail == null; } }
        // Adds one ChunkItem to the current group
        // REQUIRES: !DoneCopyingChunk && lock(this)
        private void CopyNextChunkElement()
        {
            // Try to advance the iterator on the source sequence.
            // If MoveNext returns false we are at the end, and isLastSourceElement is set to true
            isLastSourceElement = !enumerator.MoveNext();
            // If we are (a) at the end of the source, or (b) at the end of the current chunk
            // then null out the enumerator and predicate for reuse with the next chunk.
            if (isLastSourceElement || !predicate(enumerator.Current))
            {
                enumerator = null;
                predicate = null;
            }
            else
            {
                tail.Next = new ChunkItem(enumerator.Current);
            }
            // tail will be null if we are at the end of the chunk elements
            // This check is made in DoneCopyingChunk.
            tail = tail.Next;
        }
        // Called after the end of the last chunk was reached. It first checks whether
        // there are more elements in the source sequence. If there are, it 
        // Returns true if enumerator for this chunk was exhausted.
        internal bool CopyAllChunkElements()
        {
            while (true)
            {
                lock (m_Lock)
                {
                    if (DoneCopyingChunk)
                    {
                        // If isLastSourceElement is false,
                        // it signals to the outer iterator
                        // to continue iterating.
                        return isLastSourceElement;
                    }
                    else
                    {
                        CopyNextChunkElement();
                    }
                }
            }
        }
        public TKey Key { get { return key; } }
        // Invoked by the inner foreach loop. This method stays just one step ahead
        // of the client requests. It adds the next element of the chunk only after
        // the clients requests the last element in the list so far.
        public IEnumerator<TSource> GetEnumerator()
        {
            //Specify the initial element to enumerate.
            ChunkItem current = head;
            // There should always be at least one ChunkItem in a Chunk.
            while (current != null)
            {
                // Yield the current item in the list.
                yield return current.Value;
                // Copy the next item from the source sequence, 
                // if we are at the end of our local list.
                lock (m_Lock)
                {
                    if (current == tail)
                    {
                        CopyNextChunkElement();
                    }
                }
                // Move to the next ChunkItem in the list.
                current = current.Next;
            }
        }
        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
    }
}

可以这样使用:

mySourceEnumerable.ChunkBy(item => item.SomeKeyProperty)

不,很简单,这是不可能的。排序算法只有IEnumerable接口提供的信息。而从IEnumerable中是不可能得到这样的信息的。

如果您从IEnumerable派生自己的接口并添加这样的Sorted属性,这是可能的,但可能会很复杂,因为您需要为您的接口创建新的排序扩展,并且您不能使用标准的LINQ。