3

I want to split list just like string.split(split_between_that_value) does, how can I do this?

So with string "some string with spaces" string.Split(' ') will split it to [some, string, with, spaces] array.

But with list containing all characters as items I have no idea how to do it...

List:

{'s','o','m','e',' ','s','t','r','i','n','g',' ','w','i','t','h',' ','s','p','a','c','e','s'}

I want it to to split into list of lists:

{{'s','o','m','e'},{'s','t','r','i','n','g'},{'w','i','t','h'},{'s','p','a','c','e','s'}}

2
  • Is the list a List<string>? Commented Dec 3, 2016 at 20:31
  • @robert-m No my list is List<custom type> Commented Dec 3, 2016 at 20:38

3 Answers 3

2

Interestingly enough there is not an existing extension method that does this out of the box. If you wanted a general solution (something that works for more than just IEnumerable<char>) you could implement it yourself with something like this:

public static class EnumerableExtensions
{
    public static IEnumerable<IList<TSource>> Split<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
    {
        var list = new List<TSource>();

        foreach (var element in source)
        {
            if (predicate(element))
            {
                if (list.Count > 0)
                {
                    yield return list;
                    list = new List<TSource>();
                }
            }
            else
            {
                list.Add(element);
            }
        }

        if (list.Count > 0)
        {
            yield return list;
        }
    }
}

You would call it like this:

var list = new List<char>(){'s','o','m','e',' ','s','t','r','i','n','g',' ','w','i','t','h',' ','s','p','a','c','e','s'};
list.Split(x => x == ' ')
Sign up to request clarification or add additional context in comments.

5 Comments

I'm pretty sure that this one can be done with native LINQ.
Check out this SO question on splitting a list into a list of lists. It looks like you can do it using pure LINQ but it might not be as efficient. stackoverflow.com/questions/13845650/…
@BladeMight The last if (list.Count > 0) makes it a bit inconsistent compared to the string.Split behaviour in that "a".Split returns the same result as "a ".Split. I would either remove it to return all results, or add it to both yield to exclude empty entries.
@Slai - I agree that if (list.Count . 0) should have been added to the first yield return. I realized that shortly after I posted the answer but, well, I guess I just did not care enough then to change it. But it is fixed now.
The other answer and the linked answer did it too, but I couldn't figure out why.
2

I would write an extension method like

public static IEnumerable<IEnumerable<T>> GroupWhile<T>(this IEnumerable<T> seq, 
                                                        Func<T, bool> condition)
{
    List<T> list = new List<T>();
    using (var en = seq.GetEnumerator())
    {
        if (en.MoveNext())
        {
            list.Add(en.Current);

            while (en.MoveNext())
            {
                if (condition(en.Current))
                {
                    list.Add(en.Current);
                }
                else
                {
                    yield return list;
                    list = new List<T>();
                }
            }

            if (list.Any())
                yield return list;
        }
    }
}

and use it as

var input = new[]{ 's', 'o', 'm', 'e', ' ', 's', 't', 'r', 'i', 'n', 'g', ' ', 'w', 'i', 't', 'h', ' ', 's', 'p', 'a', 'c', 'e', 's' };
var result = input.GroupWhile(x => x != ' ')
             .ToList();

Comments

-1

My guess is that you might be looking for something like this:

List<char> list = "some string with spaces".ToList();

List<List<char>> lists = list.Aggregate(new List<List<char>>() { new List<char>() },
    (a, e) => { if (e == ' ') a.Add(new List<char>()); else a.Last().Add(e); return a; });

I expect this to be just a tiny bit faster than the deferred execution yield answers, but some of the List<T>.Add extra memory allocations can be avoided with List<T>.GetRange or List<T>.CopyTo:

static List<List<T>> spliT<T>(this List<T> list, T separator = default(T), int start = 0)
{
    var lists = new List<List<T>>();

    for (int i = start; i < list.Count; i++)
        if (list[i].Equals(separator))
        {
            lists.Add(list.GetRange(start, i - start));
            start = i + 1;
        }

    lists.Add(list.GetRange(start, list.Count - start));
    return lists;
}

2 Comments

Why would you 'expect this to be just a tiny bit faster than the deferred execution yield answers'? Can you elaborate because I don't have any reason to think that is the case. Both of these solutions suffer from the same problem; they eagerly load the source data. You have no idea what the source data is, you cannot know if it fits in memory. What if the consumer of your extension method only intends to Take the first n elements. Your method will still generate the entire list.
Because of the "I want it to to split into list of lists:", and that would be closer to the string.Split behaviour. My guess is that .ToList will be used on your extension to get the desired result, so I had it originally as "faster than using .ToList on the deferred execution yield answers" but shortened it before posting.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.