Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

using Linq to partition data into arrays

Tags:

c#

linq

I have an array of elements where the element has a Flagged boolean value.

1 flagged
2 not flagged
3 not flagged
4 flagged
5 not flagged
6 not flagged
7 not flagged
8 flagged
9 not flagged

I want to break it into arrays based on the flagged indicator

output >

array 1 {1,2,3}
array 2 {4,5,6,7}
array 3 {8,9}
like image 360
Kenoyer130 Avatar asked Apr 08 '10 21:04

Kenoyer130


2 Answers

Linq doesn't have an operator for this, but I've written an extension method that you may be able to use (in the process of submitting it to MoreLinq, which you should also check out):

Using the operator below, you would write:

var result = 
   items.Segment( (item,prevItem,idx) => item.Flagged )
        .Select( seq => seq.ToArray() )  // converts each sequence to an array
        .ToList();

Here's the code of the extension method:

public static IEnumerable<IEnumerable<T>> Segment<T>(IEnumerable<T> sequence, Func<T, T, int, bool> newSegmentIdentifier) 
     { 
         var index = -1; 
         using (var iter = sequence.GetEnumerator()) 
         { 
             var segment = new List<T>(); 
             var prevItem = default(T); 

             // ensure that the first item is always part 
             // of the first segment. This is an intentional 
             // behavior. Segmentation always begins with 
             // the second element in the sequence. 
             if (iter.MoveNext()) 
             { 
                 ++index; 
                 segment.Add(iter.Current); 
                 prevItem = iter.Current; 
             } 

             while (iter.MoveNext()) 
             { 
                 ++index; 
                 // check if the item represents the start of a new segment 
                 var isNewSegment = newSegmentIdentifier(iter.Current, prevItem, index); 
                 prevItem = iter.Current; 

                 if (!isNewSegment) 
                 { 
                     // if not a new segment, append and continue 
                     segment.Add(iter.Current); 
                     continue; 
                 } 
                 yield return segment; // yield the completed segment 

                 // start a new segment... 
                 segment = new List<T> { iter.Current }; 
             } 
             // handle the case of the sequence ending before new segment is detected 
             if (segment.Count > 0) 
                 yield return segment; 
         } 
     } 
like image 65
LBushkin Avatar answered Sep 24 '22 16:09

LBushkin


I had a similar problem with this, and solved it using GroupBy and closure.

//sample data
var arrayOfElements = new[] {
    new { Id = 1, Flagged = true },
    new { Id = 2, Flagged = false },
    new { Id = 3, Flagged = false },
    new { Id = 4, Flagged = true },
    new { Id = 5, Flagged = false },
    new { Id = 6, Flagged = false },
    new { Id = 7, Flagged = false },
    new { Id = 8, Flagged = true },
    new { Id = 9, Flagged = false }
};

//this is the closure which will increase each time I see a flagged
int flagCounter = 0;

var query = 
    arrayOfElements.GroupBy(e => 
        {
            if (e.Flagged)
                flagCounter++;
            return flagCounter;
        });

What it does is grouping on an int (flagCounter), which is increased each time a Flagged element is found.
Please note this won't work with AsParallel().

Testing the results:

foreach(var group in query)
{
    Console.Write("\r\nGroup: ");
    foreach (var element in group)
        Console.Write(element.Id);
}

Outputs:

Group: 123
Group: 4567
Group: 89

like image 41
Alex Bagnolini Avatar answered Sep 22 '22 16:09

Alex Bagnolini