morelinq / MoreLINQ

Extensions to LINQ to Objects
https://morelinq.github.io/
Apache License 2.0
3.67k stars 412 forks source link

Spread tuples: [(x, x), (x, x), …] → [x, x, x, x, …] #939

Open atifaziz opened 1 year ago

atifaziz commented 1 year ago

I propose to add a new operator that takes a sequence of tuples and spreads the elements of each tuple into the resulting sequence:

public static IEnumerable<T> Spread<T>(this IEnumerable<(T, T)> source)
{
    foreach (var (a, b) in source)
    {
        yield return a;
        yield return b;
    }
}

Additional overloads can be supplied for remaining multary tuple types.

Example:

using MoreLinq;

var xs =
    Enumerable.Range(1, 10)
              .Zip(MoreEnumerable.Return(0).Repeat())
           // ...above same as...
           // .Select(x => (x, 0))
              .Spread();

foreach (var x in xs)
    Console.WriteLine(x);

Outputs:

1
0
2
0
3
0
4
0
5
0
6
0
7
0
8
0
9
0
10
0

This would be more generally usable than what's proposed in #695.

viceroypenguin commented 1 year ago

I would argue this is a fancier more opinionated version of Unfold, so I'd rather see Unfold be updated to support this type of behavior. Suggestions: a) an overload that takes IEnumerable<> and effectively does source.SelectMany(x => Unfold(x)) b) an overload that takes ValueTuple and shortcuts the other parameters c) both of the above?

atifaziz commented 1 year ago

@viceroypenguin Do you mean Unfold that's a sequence generator? I guess I'm not seeing how the above is a fancier version of that. Could you expand/clarify?

viceroypenguin commented 1 year ago

Sure, what you're describing here is basically sequence generation for a single ValueTuple (i.e. (t, t) => [t, t]), and a Flatten operation on the sequence of sequences. In effect, Spread(source) => source.SelectMany(x => new { x.Item1, x.Item2, }).

Reversing operation: IEnumerable<(T, T)> Unspread(IEnumerable<T> source) => source.Batch(2).Fold((x, y) => (x, y));

atifaziz commented 1 year ago

Ah, you mean a generator for inner sequence? So you're saying (a) is the more general version of (b)? Do you mean the below?

public static IEnumerable<U> Spread<T, U>(this IEnumerable<T> source, Func<T, (U, U)> selector)
{
    foreach (var item in source)
    {
        var (a, b) = selector(item);
        yield return a;
        yield return b;
    }
}
viceroypenguin commented 1 year ago

a) and b) are orthogonal to each other. Unfold as an operator works on an item right now.

a) Unfold as an operator on IEnumerable<T> should be: source.SelectMany(x => Unfold(x, ...)); b) I would argue that expanding a ValueTuple to an IEnumerable<T> is effectively an Unfold operation, just with specific opinionated semantics on what it means to Unfold a ValueTuple c) would be the combination of both, i.e. the cartesian of both above ideas applied to Unfold.

This is just my argument, feel free to ignore if I'm wrong. :)

declard commented 1 year ago

That looks like

public static IEnumerable<T> AsEnumerable<T>(this (T Left, T Right) pair)
{
    yield return pair.Left;
    yield return pair.Right;
}
atifaziz commented 1 year ago

@declard Not quite. That would be (x, x) → [x, x] whereas this is, as in the title, [(x, x), (x, x), …] → [x, x, x, x, …].