Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linq select items up until next occurrence

Tags:

c#

.net

linq

I need to filter the following list to return all items beginning with the first item that starts with "Group", up until, but not including the next item that starts with "Group" (or the up until the last item).

List<string> text = new List<string>();
text.Add("Group Hear It:");
text.Add("    item: The Smiths");
text.Add("    item: Fernando Sor");
text.Add("Group See It:");
text.Add("    item: Longmire");
text.Add("    item: Ricky Gervais Show");
text.Add("    item: In Bruges");

After filtering, I want to have the following items in the first grouping:

"Group Hear It:"
"    item: The Smiths"
"    item: Fernando Sor"

And the following items in the second grouping:

"Group See It:"
"    item: Longmire"
"    item: Ricky Gervais Show"
"    item: In Bruges"

This doesn't work because I'm filtering the list in the first where to exclude the "item:" items... Am I close with TakeWhile, or way off?

var group = text.Where(t => t.StartsWith("Group ")))
   .TakeWhile(t => t.ToString().Trim().StartsWith("item"));
like image 806
OldBuildingAndLoan Avatar asked Jul 16 '13 00:07

OldBuildingAndLoan


2 Answers

try this:

var i = 0;
var groups = text.GroupBy(t => t.StartsWith("Group") ? ++i : i);

i holds the number of times we've seen the group condition. using i++ instead of ++i would let the condition complete a group instead of start it.

like image 51
Patrick Hallisey Avatar answered Oct 19 '22 14:10

Patrick Hallisey


You can do this rather cleanly with the help of a generator. The generator will keep track of what key is currently in use, something you can't do with a traditional LINQ query without introducing external variables. You'll just need to decide when the key should change as you go through the collection. Once you get the key to use for each item, just group them by that key.

public static class Extensions
{
    public static IEnumerable<IGrouping<TKey, TResult>> ConsecutiveGroupBy<TSource, TKey, TResult>(
        this IEnumerable<TSource> source,
        Func<TSource, bool> takeNextKey,
        Func<TSource, TKey> keySelector,
        Func<TSource, TResult> resultSelector)
    {
        return
            from kvp in AssignKeys(source, takeNextKey, keySelector)
            group resultSelector(kvp.Value) by kvp.Key;
    }

    private static IEnumerable<KeyValuePair<TKey, TSource>> AssignKeys<TSource, TKey>(
        IEnumerable<TSource> source,
        Func<TSource, bool> takeNextKey,
        Func<TSource, TKey> keySelector)
    {
        var key = default(TKey);
        foreach (var item in source)
        {
            if (takeNextKey(item))
                key = keySelector(item);
            yield return new KeyValuePair<TKey, TSource>(key, item);
        }
    }
}

Then to use it:

var lines = new List<string>
{
    "Group Hear It:",
    "    item: The Smiths",
    "    item: Fernando Sor",
    "Group See It:",
    "    item: Longmire",
    "    item: Ricky Gervais Show",
    "    item: In Bruges",
};

var query = lines.ConsecutiveGroupBy(
    line => line.StartsWith("Group"),
    line => line,
    line => line);
like image 30
Jeff Mercado Avatar answered Oct 19 '22 12:10

Jeff Mercado