-2

I have a list that is made up of a collection of smaller lists. I need to split that aggregated list into smaller lists based on a specific value.

I have tried to use the .Take() method but that takes in an integer and I do not necessarily know how long the smaller list is going to be.

My list large list looks something like this

List<T> Foo = new List<T>();
Foo.Add("Test1", "LineTypeA");
Foo.Add("Test2", "LineTypeA");
Foo.Add("Test3", "LineTypeB");
Foo.Add("Test4", "LineTypeA");
Foo.Add("Test5", "LineTypeB");

List<List<T> Bar = new List<ListT>>();
Bar.Add(Foo);

I need to be able to split "Bar" into smaller lists based on "LineTypeB". So one list would contain 3 elements, and another list would contain two from the example above. There is a similar solution in this thread to do what I want but in python - Python splitting a list based on a delimiter word

Kizzle
  • 101
  • 3
  • 17
  • It's not clear what your data looks like. Is `Foo` a `List>` where each sub-list has two values or is `Foo` a `List` where `SomeType` is a combination of two strings and you meant to do something like `Foo.Add(new SomeType("Test1", "LineTypeA"));`? – juharr Jul 04 '19 at 16:50
  • Apologies for not being more thorough. Foo is a list of SomeType. I would then need Bar to loop through Foo and add values to a list until a certain value is found. In the example above Bar would loop through Foo and add the values to a list until the entry is reached that contains "LineTypeB" and then a second list made up of the remaining values up until the final "LineTypeB" – Kizzle Jul 04 '19 at 19:09

2 Answers2

0

Let's assume that you have your type which your list will contain:

public class MyItem
{
    public string Key { get; set; }

    public string Value { get; set; }

    public MyItem(string key, string value)
    {
        Key = key;
        Value = value;
    }
}

And you have initial lists created like this:

List<MyItem> Foo1 = new List<MyItem>();
Foo1.Add(new MyItem("Test1", "LineTypeA"));
Foo1.Add(new MyItem("Test2", "LineTypeA"));
Foo1.Add(new MyItem("Test3", "LineTypeB"));
Foo1.Add(new MyItem("Test4", "LineTypeA"));
Foo1.Add(new MyItem("Test5", "LineTypeB"));

List<MyItem> Foo2 = new List<MyItem>();
Foo2.Add(new MyItem("Test1", "LineTypeA"));
Foo2.Add(new MyItem("Test2", "LineTypeA"));
Foo2.Add(new MyItem("Test3", "LineTypeB"));
Foo2.Add(new MyItem("Test4", "LineTypeA"));
Foo2.Add(new MyItem("Test5", "LineTypeB"));

List<MyItem> Foo3 = new List<MyItem>();
Foo3.Add(new MyItem("Test1", "LineTypeA"));
Foo3.Add(new MyItem("Test2", "LineTypeA"));
Foo3.Add(new MyItem("Test3", "LineTypeB"));
Foo3.Add(new MyItem("Test4", "LineTypeA"));
Foo3.Add(new MyItem("Test5", "LineTypeB"));


List<List<MyItem>> Bar = new List<List<MyItem>> ();
Bar.Add(Foo1);
Bar.Add(Foo2);
Bar.Add(Foo3);

Next, you can use LINQ to make a flat list (maybe it is not even needed if you will have just one inner List e.g. only Foo1):

List<MyItem> flatList = Bar.SelectMany(innerList => innerList).ToList();
string expectedValue = "LineTypeB";

And now to the most important part, you can do it by creating extension method for a list:

public static class ListExtensionMethods
{
    public static List<List<MyItem>> SplitListByValue(this List<MyItem> list, string value)
    {
        List<List<MyItem>> splittedList = new List<List<MyItem>>() { new List<MyItem>() }; // initial set

        for (int i = 0; i < list.Count; i++)
        {
            var currentItem = list[i];
            if (i == 0 && currentItem.Value == value) //if the first element already meets the condition, add it to the first sublist and create new one
            {
                splittedList.LastOrDefault().Add(currentItem);
                splittedList.Add(new List<MyItem>());
            }
            else if (currentItem.Value == value)
            {
                splittedList.Add(new List<MyItem>()); //if i-th element meets the condition, create new sublist and add it there
                splittedList.LastOrDefault().Add(currentItem);
            }
            else
            {
                splittedList.LastOrDefault().Add(currentItem);
            }
        }

        return splittedList;
    }
}

And then you call it like it follows on your flatList:

var splitList = flatList.SplitListByValue(expectedValue);

I hope this helps.

zpouip
  • 787
  • 5
  • 11
  • The layout of your answer in terms of the different lists is correct @zpouip however that's not how I want to create these new lists based on a certain value. In my above example, I'd need to loop through the list until "LineTypeB" is found, and add that value and all before it to a new list. This process repeats until I'm left with something like List1 = "Test1", "LineTypeA", "Test2", "LineTypeA", "Test3", "LineTypeB" and List2 = "Test4", "LineTypeA", "Test5", "LineTypeB". Formatting sucks because I'm on mobile. Hope this clears it up a little. – Kizzle Jul 04 '19 at 20:07
  • Ah ok, now its much clearer, I will update my answer accordingly. – zpouip Jul 05 '19 at 07:19
  • @Kizzle I updated my answer. Please note in my extension method that when I find element with the expected value, I immediately cut it and create new sublist. I am not sure if you want to do it this way. If that is not the case, you will just have to update else if statement (probably you can just switch the order of two lines inside. – zpouip Jul 05 '19 at 07:48
  • You're a genius @zpouip – Kizzle Jul 05 '19 at 10:01
  • You can just use `Last` instead of `LastOrDefault` as you know the main list always has at least one sub-list. – juharr Jul 05 '19 at 11:44
0

Here's the C# equivalent of the answer in the Python question. I changed it to not take a delimiter and instead take a delegate that determines if the item is a delimiter. Also I fixed it to not return empty collections when the collection is empty or the first item matches.

public static IEnumerable<IEnumerable<T>> SplitOn<T>(
    this IEnumerable<T> items, 
    Func<T, bool> split)
{
    var g = new List<T>();
    foreach(var item in items)
    {
        if(split(item) && g.Count > 0)
        {
            yield return g;
            g = new List<T>();
        }

        g.Add(item);
    }

    if(g.Count > 0) yield return g;
}

Then you can use it with your example like this.

var results = Bar.SelectMany(x => x).SplitOn(x => x.Value == "LineTypeB");
juharr
  • 31,741
  • 4
  • 58
  • 93