Equivalent in C# to Haskell's Data.List.Span

Viewed 269

Hello is there any efficient method implemented already to get the functionality of Haskell Data.List.span?

span :: (a -> Bool) -> [a] -> ([a], [a])

Basically given a list and a predicate i want to split the list in two after the first occurence of a false predicate.The elements after the pivot element that tests False may or may not respect the predicate , but I do not care.

List: [1,2,3,1,2,3]
Predicate: x<3
Span:  `span  (x<3) [1,2,3,1,2,3]`   =>  `([1,2],[3,1,2,3])`

Update I do not care of the elements after the first false predicate.I just want to split the list at the first occurence of False predicate. The sequence can be True after the first False predicate but I still want to split it.

7 Answers

You could make use of TakeWhile and Skip:

public static IEnumerable<IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
    var first = enumerable.TakeWhile(predicate);
    yield return first;
    var second = enumerable.Skip(first.Count());
    yield return second;
}

Update

To avoid multiple iterations, and not requiring the use of a list or array:

public static IEnumerable<IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
    yield return enumerable.TakeWhile(predicate);
    yield return enumerable.TakeAfter(predicate);
}

public static IEnumerable<T> TakeAfter<T>(this IEnumerable<T> enumerable, Func<T, bool> predicate)
{
    bool yielding = false;
    foreach (T item in enumerable)
    {
        if (yielding = yielding || !predicate(item))
        {
            yield return item;
        }
    }
}

If you are happy with using lists, then you can make a single pass through the source list to create two new lists, like so:

public static (List<T> part1, List<T> part2) SplitListBy<T>(List<T> source, Predicate<T> splitWhen)
{
    var part1 = new List<T>();

    int i;

    for (i = 0; i < source.Count && !splitWhen(source[i]); ++i)
        part1.Add(source[i]);

    var part2 = source.GetRange(i, source.Count - i);

    return (part1, part2);
}

This should be extremely performant. Note that this uses a tuple to return the two lists, which requires C# 7 or later. If you can't use c# 7+, you'll have to change the code to use an out parameter to return one of the lists.

Test code:

var list = new List<int>{ 1, 2, 3, 1, 2, 3 };

var (part1, part2) = SplitListBy(list, item => item >= 3);

Console.WriteLine(string.Join(", ", part1));
Console.WriteLine(string.Join(", ", part2));

Output:

1, 2
3, 1, 2, 3

If you don't need two new lists, but just want to use the original list for one part and a single new list for the other part, you can do it like this:

public static List<T> SplitListBy<T>(List<T> source, Predicate<T> splitWhen)
{
    int i;

    for (i = 0; i < source.Count && !splitWhen(source[i]); ++i)
        ;

    var part2 = source.GetRange(i, source.Count - i);

    source.RemoveRange(i, source.Count - i);

    return part2;
}

Test code for this is very similar:

var list = new List<int>{ 1, 2, 3, 1, 2, 3 };

var part2 = SplitListBy(list, item => item >= 3);

Console.WriteLine(string.Join(", ", list));
Console.WriteLine(string.Join(", ", part2));

(Output is the same as the other test code.)

At the time that I'm writing this answer, I don't think that any of the other answers faithfully replicate Haskell's span function. That's okay, you may actually be looking for something else, but I wanted to add this for completion's sake.

First, you can't necessarily assume that span only iterates over the input list once. It's difficult to reason about Haskell's run-time behaviour because of its lazy evaluation, but consider this list:

xs = [trace "one" 1, trace "two" 2, trace "three" 3,
      trace "one" 1, trace "two" 2, trace "three" 3]

Here I've deliberately used trace from Debug.Trace so that we can observe what's going on. Specifically, I want to point your attention to what happens if you iterate over the lists independently, as one would probably do in 'real' code:

Prelude Data.List Debug.Trace> (l, r) = span (< 3) xs
Prelude Data.List Debug.Trace> l
one
[1two
,2three
]

Iterating over the first list stops at the first value that evaluates to False, so that's fine and efficient. That's not the case, however, when you print the second list:

Prelude Data.List Debug.Trace> r
one
two
three
[3,one
1,two
2,three
3]

Notice that while it only prints [3, 1, 2, 3], it iterates over the entire list. How could it do otherwise? It's a function. It doesn't maintain a bookmark over how far it's already iterated the list.

On the other hand, the function does handle infinite lists:

Prelude Data.List> take 10 $ fst $ span (< 3) $ repeat 1
[1,1,1,1,1,1,1,1,1,1]
Prelude Data.List> take 10 $ fst $ span (< 3) $ repeat 3
[]
Prelude Data.List> take 10 $ snd $ span (< 3) $ repeat 3
[3,3,3,3,3,3,3,3,3,3]

As far as I can tell, few of the other answers (as I'm writing this) handle infinite lists.

In C#, lazily evaluated lists are modelled with IEnumerable<T>, so the best I've been able to come up with is this:

public static (IEnumerable<T>, IEnumerable<T>) Span<T>(
    this IEnumerable<T> source,
    Func<T, bool> pred)
{
    return (source.TakeWhile(pred), source.SkipWhile(pred));
}

which, admittedly, is hardly above the Fairbairn threshold. It does, however, handle infinite sequences in the same way as span does:

> var (left, right) = new[] { 1, 2, 3, 1, 2, 3 }.Span(x => x < 3);
> left
TakeWhileIterator { 1, 2 }
> right
SkipWhileIterator { 3, 1, 2, 3 }
> var (left, right) = 1.RepeatInfinite().Span(x => x < 3);
> left.Take(10)
TakeIterator { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }
> var (left, right) = 3.RepeatInfinite().Span(x => x < 3);
> right.Take(10)
TakeIterator { 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 }
> left.Take(10)
TakeIterator { }

the simplest way to use ToLookup

example

var listInt = new List<int>{1, 2, 3, 4, 5, 6};
var result = listInt.ToLookup(x => x > 3);

Result

[[1,2,3], [4,5,6]]

Edit

var listInt = new List<int> { 1, 2, 3, 1, 2, 3 };

Create an extension method

        public static IEnumerable<T> TakeUntil<T>(this IEnumerable<T> source, Func<T, bool> predicate)
        {
            foreach (var item in source)
            {
                if (!predicate(item))
                    break;
                yield return item;
            }
        }

and call it

var first = listInt.TakeUntil(x => x < 3);
var second = listInt.Skip(first.Count());

Result

first = [1,2]

second = [3, 1, 2, 3]

I don't think there is a native .NET Framework or .NET Core method that does this, so you'll probably have to write your own. Here is my extension method implementation of this:

public static Tuple<IEnumerable<T>, IEnumerable<T>> SplitWhen<T>(this IEnumerable<T> self, Func<T, bool> func)
{
    // Enumerate self to an array so we don't do it multiple times
    var enumerable = self as T[] ?? self.ToArray();
    var matching = enumerable.TakeWhile(func).ToArray();
    var notMatching = enumerable.Skip(matching.Length);

    return new Tuple<IEnumerable<T>, IEnumerable<T>>(matching, notMatching);
}

This method will return a tuple with tuple.Item1 being the part of the list that matches the predicate, and tuple.Item2 being the rest of the list.

This method needs to be declared in a separate static class as it is an extension method for IEnumerable<T>. You can also use Tuple construction/ deconstruction if you want to name Item1 and Item2 something different

I believe you are looking for a c# IEnumerable. You could write for example

IEnumerable<int> list = new List<int> { 1,2,3,4,5,6};
var list1 = list.Where(x=>x>3); //deferred execution
var list2 = list.Where(x=>x<=3); //deferred execution

I have accepted @Matthew Watson solution.Though i will also post a little modified version using the Span and ReadOnlyMemory

public static (IEnumerable<T>first,IEnumerable<T> second) Span<T>(this ReadOnlyMemory<T> original,Func<T,bool> predicate) {
            List<T> list = new List<T>();

            int splitIndex = 0;
            for (int i = 0; i < original.Length && !predicate(original.Span[i]); i++) {
                list.Add(original.Span[splitIndex=i]);
            }
            var part2 = original.Slice(splitIndex);
            return (list, part2.ToArray());
        }
Related