Skip to content

Proposal: add a TakeUntil for Seq #1478

@alsi-lawr

Description

@alsi-lawr

More of a convenience instead of rolling my own extension, it'd be nice to have a declarative TakeUntil in addition to TakeWhile that includes every element up to and including the element that fails the predicate. Imagine something like this:

public static string TransformLine(string line)
{
    return line;
}


public static IEnumerable<string> TransformLines(this IEnumerable<string> lines)
{
    foreach (var line in lines)
    {
        if(!SomeConditionIsMet(line))
        {
            yield return "MyString";
            yield break;
        }
        yield return TransformLine(line);
    }
}

If I want to do something like this using Seq using a more composable and declarative approach, I'd need to do something like:

public static Seq<string> TransformLines(this Seq<string> lines)
    => pipe(
           lines.TakeWhile(SomeConditionIsMet).Map(TransformLine),
           seq => lines.Exists(line => !SomeConditionIsMet(line)) ? seq.Add("MyString") : seq
       );

whereas, if there's a TakeUntil, you can absorb the funky logic and just do:

public static string TransformLine(string line)
{
    return SomeConditionIsMet(line) ? line : "MyString";
}


public static Seq<string> TransformLines(this Seq<string> lines)
    => lines.TakeUntil(SomeConditionIsMet).Map(TransformLine);

A naïve implementation would be something like

    /// <summary>
    /// Iterate the sequence, yielding items until one is encountered
    /// that does not match the predicate provided.
    /// </summary>
    /// <returns>
    /// A new sequence with the first items that match the 
    /// predicate and the first element that did not match the predicate.
    /// </returns>
    [Pure]
    public Seq<A> TakeUntil(Func<A, bool> pred)
    {
        return new Seq<A>(new SeqLazy<A>(Yield(Value, pred)));
        static IEnumerable<A> Yield(IEnumerable<A> xs, Func<A, bool> f)
        {
            foreach (var x in xs)
            {
                if (!f(x))
                {
                    yield return x;
                    yield break;
                }

                yield return x;
            }
        }
    }

To me, this is conceptually equivalent in terms of how to

TakeWhile() => for(int i = 0; i < bound; i++)
TakeUntil() => for(int i = 0; i <= bound; i++)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions