【问题标题】:LINQ query to split an ordered list into sublists of contiguous points by some criteriaLINQ 查询按某些标准将有序列表拆分为连续点的子列表
【发布时间】:2023-03-19 06:20:01
【问题描述】:

寻求帮助编写有关某些对象的 LINQ 查询。我觉得如果我的 LINQ 技能更像忍者,我可以使用一些聪明的 GroupBy/SelectMany(或其他东西?!)来做到这一点。

一般来说,问题是:给定一个按某种顺序排列的对象列表,其中每个对象都有一个标志,我如何将列表拆分为子列表,其中每个子列表都是其中的所有连续点标志设置了吗?

执行此操作的命令式方法类似于以下伪代码:

foreach object obj
  if(obj.FlagSet) 
    add it to my currentsublist
  else
    skip to the next obj where FlagSet and start a new sublist

所以,给定以下输入:

{ 1, Flag }, { 2, Flag }, {3, NoFlag }, {4, Flag}, {5, NoFlag}, {6, Flag}...

我想要以下输出:

列表 1:{1, 2} 清单 2:{4} 清单 3:{6}

我想通过 LINQ 在功能上做到这一点。有什么想法吗?

(我首先环顾四周,但我能看到的所有问题似乎都想简单地对列表进行分组或拆分为相等的大小,这对我没有帮助。)

【问题讨论】:

  • 天啊!通常,在发布此消息大约一秒钟后,我发现了 .TakeWhile() 方法,它看起来非常有用!任何答案仍然很感激。

标签: c# linq split


【解决方案1】:

这篇 MSDN 文章提供了按连续值分组的代码:

http://msdn.microsoft.com/en-us/library/cc138361.aspx

我已经从上面的链接中复制了代码,以防链接腐烂:

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

它不漂亮,但效果很好。

在你的情况下,它会是这样的:

myList.ChunkBy( o => o.FlagSet )

【讨论】:

  • 这太棒了。不敢相信谷歌从来没有为我提供过。 +1,谢谢:)
【解决方案2】:

除了@spenders excellent link (+1!),我还要补充:

它很漂亮,而且效果很好:

  • 它完全在惰性模式下运行
  • 它是线程安全的
  • 通过提供实现IGrouping&lt;&gt;Chunk&lt;&gt; 集成到标准linq
  • 它存在一些样式问题(命名、范围过大、缺少 m_lock 的只读修饰符;诸如此类)

我现在看到的唯一真正抱怨是它未能主动处理从可枚举源中获取的枚举器。这是我的相关修复:

public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
{
    //  ...
    using (var enumerator = source.GetEnumerator()) // <--- FIXED
    {

更新

这是我完全修改的来源,修复了上面列出的所有问题。 **这也使Chunk&lt;&gt; 一次性:

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

namespace ChunkIt
{
    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)
        {
            const bool noMoreSourceElements = true;

            using (var enumerator = source.GetEnumerator())
            {
                if (!enumerator.MoveNext()) 
                    yield break;

                Chunk<TKey, TSource> current;
                while (true)
                {
                    var key = keySelector(enumerator.Current);
                    current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value)));

                    yield return current;

                    if (current.CopyAllChunkElements() == noMoreSourceElements)
                        yield break;
                }
            }
        }

        class Chunk<TKey, TSource> : IGrouping<TKey, TSource>, IDisposable
        {
            class ChunkItem
            {
                public ChunkItem(TSource value)
                {
                    Value = value;
                }
                public readonly TSource Value;
                public ChunkItem Next;
            }

            private readonly TKey _key;
            private IEnumerator<TSource> _enumerator;
            private Func<TSource, bool> _predicate;
            private readonly ChunkItem _head;
            private ChunkItem _tail;
            private bool _isLastSourceElement;
            private readonly object _mLock;

            public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
            {
                _key = key;
                _enumerator = enumerator;
                _predicate = predicate;

                _head = new ChunkItem(enumerator.Current);

                _tail = _head;

                _mLock = new object();
            }

            private bool DoneCopyingChunk { get { return _tail == null; } }

            private void CopyNextChunkElement()
            {
                _isLastSourceElement = !_enumerator.MoveNext();

                if (_isLastSourceElement || !_predicate(_enumerator.Current))
                {
                    _enumerator = null;
                    _predicate = null;
                }
                else
                {
                    _tail.Next = new ChunkItem(_enumerator.Current);
                }

                _tail = _tail.Next;
            }

            internal bool CopyAllChunkElements()
            {
                while (true)
                    lock (_mLock)
                    {
                        if (DoneCopyingChunk)
                            return _isLastSourceElement;

                        CopyNextChunkElement();
                    }
            }

            public TKey Key { get { return _key; } }

            public IEnumerator<TSource> GetEnumerator()
            {
                ChunkItem current = _head;

                while (current != null)
                {
                    yield return current.Value;

                    lock (_mLock)
                        if (current == _tail)
                            CopyNextChunkElement();

                    current = current.Next;
                }
            }

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

            #region Implementation of IDisposable

            public void Dispose()
            {
                if (null!=_enumerator)
                    _enumerator.Dispose();

            }

            #endregion
        }
    }
}

【讨论】:

  • update 发布了一个略微改进的版本,该版本也使 Chunk 类一次性使用。顺便说一句,我浓缩了大量注释的代码(主要用于 SO)
  • 很好的发现与 Enumerators 的处置。 +1
【解决方案3】:

我认为你管理两个列表的方式 并且在其他过去只是将第一个列表添加到最终列表并清除第一个列表 并在设置标志时将对象添加到第一个列表

【讨论】:

    【解决方案4】:

    通过 LINQ

    var list = new[]
    {
        new KeyValuePair<string, string>("A", "We"),
        new KeyValuePair<string, string>("A", "Think"),
        new KeyValuePair<string, string>("A", "That"),
        new KeyValuePair<string, string>("B", "Linq"),
        new KeyValuePair<string, string>("C", "Is"),
        new KeyValuePair<string, string>("A", "Really"),
        new KeyValuePair<string, string>("B", "Cool"),
        new KeyValuePair<string, string>("B", "!")
    };
    var queryGroup = list.Select(data => data).
        GroupBy(g => g.Key, //group key
                i => i.Value //value
        );
    foreach (var group in queryGroup)
    {
        foreach (var data in group)
        {
    
        }
    }
    //even/odd grouping
    int groupCount = 2;
    var queryEvenOdd = list.Select((data, index) => new { data, index }).
            GroupBy(
            g => g.index % groupCount,//group key
            i => i.data //value
            );
    

    【讨论】:

      猜你喜欢
      • 2014-05-17
      • 2010-09-29
      • 2019-09-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多