Linq selects items until next appearance

I need to filter the following list to return all elements starting from the first element starting with “Group”, up to the next but not including the next element that starts with “Group” (or until the last element).

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 elements in the first group:

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

And the following elements in the second grouping:

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

This does not work because I am filtering the list in the first where to exclude the "item:" elements. Am I closing using TakeWhile or is it gone?

 var group = text.Where(t => t.StartsWith("Group "))) .TakeWhile(t => t.ToString().Trim().StartsWith("item")); 
+7
c # linq
source share
6 answers

Like Jeff Mercado, but does not recycle the entire list:

 public static class Extensions { public static IEnumerable<IList<T>> ChunkOn<T>(this IEnumerable<T> source, Func<T, bool> startChunk) { List<T> list = new List<T>(); foreach (var item in source) { if(startChunk(item) && list.Count > 0) { yield return list; list = new List<T>(); } list.Add(item); } if(list.Count > 0) { yield return list; } } } 

Use as:

 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"); var chunks = text.ChunkOn(t => t.StartsWith("Group")); 
+10
source share

You can do this quite neatly with a generator. The generator will track which key is currently in use, which you cannot do with the traditional LINQ query without introducing external variables. You just need to decide when the key should change as you progress through the collection. Once you get a key to use for each item, just group it with 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); 
+2
source share

One way is to use the class and use LINQ to get the results from the class:

  public class MediaItem { public MediaItem(string action, string name) { this.Action = action; this.Name = name; } public string Action = string.Empty; public string Name = string.Empty; } List<MediaItem> mediaItemList = new List<MediaItem>(); mediaItemList.Add(new MediaItem("Group: Hear It", "item: The Smiths")); mediaItemList.Add(new MediaItem("Group: Hear It", "item: Fernando Sor")); mediaItemList.Add(new MediaItem("Group: See It", "item: Longmire")); mediaItemList.Add(new MediaItem("Group: See It", "item: Ricky Gervais Show")); mediaItemList.Add(new MediaItem("Group: See It", "item: In Bruges")); var results = from item in mediaItemList.AsEnumerable() where item.Action == "Group: Hear It" select item.Name; foreach (string name in results) { MessageBox.Show(name); } 

Another way is to use LINQ yourself:

  // Build the list 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"); text.Add("Group Buy It:"); text.Add(" item: Apples"); text.Add(" item: Bananas"); text.Add(" item: Pears"); // Query the list and create a "table" to work with var table = from t in text select new { Index = text.IndexOf(t), Item = t, Type = t.Contains("Group") ? "Group" : "Item", GroupIndex = t.Contains("Group") ? text.IndexOf(t) : -1 }; // Get the table in reverse order to assign the correct group index to each item var orderedTable = table.OrderBy(i => i.Index).Reverse(); // Update the table to give each item the correct group index table = from t in table select new { Index = t.Index, Item = t.Item, Type = t.Type, GroupIndex = t.GroupIndex < 0 ? orderedTable.Where( i => i.Type == "Group" && i.Index < t.Index ).First().Index : t.GroupIndex }; // Get the "Hear It" items from the list var hearItItems = from g in table from i in table where i.GroupIndex == g.Index && g.Item == "Group Hear It:" select i.Item; // Get the "See It" items from the list var seeItItems = from g in table from i in table where i.GroupIndex == g.Index && g.Item == "Group See It:" select i.Item; // Get the "Buy It" items I added to the list var buyItItems = from g in table from i in table where i.GroupIndex == g.Index && g.Item == "Group Buy It:" select i.Item; 
+1
source share

try the following:

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

i contains the number of cases when we saw a group condition. using i ++ instead of ++, I would allow the condition to populate the group, rather than run it.

+1
source share

This may require peremptory code; I cannot come up with a LINQ solution.

 List<List<string>> results = new List<List<string>>(); List<string> currentGroup = null; foreach (var item in text) { if (item.StartsWith("Group")) { if (currentGroup != null) results.Add(currentGroup); currentGroup = new List<string>(); } currentGroup.Add(item); } results.Add(currentGroup); 
0
source share

You cannot do this with simple LINQ and lambda expressions. You can define a delegate method that refers to a logical sign that is in the outer scope (for example, in the instance where it is executed), then pass it to the select statement, but I think a normal loop would be better. If you do this, you can simply mark the start and end index and then extract the range, this will probably be the cleanest solution.

0
source share

All Articles