2011-09-19 21 views
7

Tìm trợ giúp viết truy vấn LINQ trên một số đối tượng. Tôi cảm thấy nếu kỹ năng LINQ của tôi là ninja hơn tôi có thể làm điều này với một số thông minh GroupBy/SelectMany (hoặc một cái gì đó ?!).Truy vấn LINQ để chia danh sách được sắp xếp thành danh sách con của các điểm tiếp giáp theo một số tiêu chí

Nói chung, câu hỏi là: đưa ra danh sách đối tượng theo thứ tự nào đó, trong đó mỗi đối tượng có Cờ, cách chia danh sách thành các danh sách phụ, trong đó mỗi danh sách con là tất cả các điểm tiếp giáp cờ được đặt?

Một cách bắt buộc để làm điều này sẽ như thế nào giả sau đây:

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

Vì vậy, do đầu vào như sau:

{1, Cờ}, {2, Cờ}, {3, NoFlag}, {4, Cờ}, {5, NoFlag}, {6, Cờ} ...

tôi muốn đầu ra sau đây:

Danh sách 1: {1, 2} Danh sách 2 : {4} Danh sách 3: {6}

Và tôi muốn thực hiện chức năng này qua LINQ. Bất kỳ ý tưởng?

(Tôi đã nhìn quanh đầu tiên, nhưng tất cả những câu hỏi tôi có thể thấy dường như muốn hoặc chỉ đơn giản là nhóm một danh sách hoặc chia thành các kích cỡ bằng nhau, mà đã không hữu ích cho tôi.)

+0

D'oh! Thông thường, khoảng một giây sau khi đăng bài này, tôi phát hiện ra phương thức .TakeWhile() có vẻ rất hữu dụng! Bất kỳ câu trả lời vẫn được đánh giá cao mặc dù. – randomsequence

Trả lời

11

MSDN này bài viết cung cấp mã vào nhóm bởi các giá trị tiếp giáp:

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

tôi đã sao chép mã từ liên kết ở trên trong trường hợp liên kết thối:

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

Nó không đẹp, nhưng hoạt động tốt.

Trong trường hợp của bạn nó sẽ là một cái gì đó như:

myList.ChunkBy(o => o.FlagSet) 
+0

Điều này thật tuyệt vời. Không thể tin rằng google không bao giờ biến điều đó cho tôi. 1 và cảm ơn :) – randomsequence

0

Tôi nghĩ rằng cách này là bạn quản lý hai danh sách và quá khứ khác chỉ cần thêm danh sách đầu tiên vào danh sách chính thức và rõ ràng danh sách đầu tiên và thêm đối tượng để danh sách đầu tiên khi lá cờ được thiết lập

6

Ngoài @spenders excellent link (+1!), tôi muốn thêm:

đó là đẹp, và nó hoạt động tốt:

  • nó hoạt động hoàn toàn trong chế độ lười biếng
  • nó là an toàn
  • chủ đề tích hợp vào LINQ tiêu chuẩn bằng cách cung cấp Chunk<> mà thực hiện IGrouping<>
  • nó có một số vấn đề phong cách (đặt tên, phạm vi quá mức, thiếu modifier readonly cho m_lock; những thứ như thế)

Các chỉ thực chuôi tôi thấy ngay bây giờ là nó thất bại trong việc ủng hộ tích cực xử lý các điều tra viên nó được từ nguồn đếm được.Dưới đây là sửa chữa liên quan của tôi:

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 
    { 

Cập nhật

Đây là nguồn hoàn toàn sửa đổi của tôi, sửa chữa tất cả các vấn đề nêu trên. ** Điều này cũng làm Chunk<> dùng một lần:

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 
     } 
    } 
} 
+0

Ngoài ra +1 đến điểm này, thực sự tốt :) – randomsequence

+0

** cập nhật ** xuất bản một phiên bản cải tiến một chút cũng làm cho lớp Chunk dùng một lần. Bằng cách này tôi ngưng tụ các mã nhận xét rất nhiều (chủ yếu cho SO) – sehe

+1

Rất dễ tìm với việc xử lý các Điều tra viên. +1 – spender

-1

By 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 
     ); 
Các vấn đề liên quan