Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add overloads that return tuples as default projections #598

Draft
wants to merge 53 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7a084f4
Tuple-returning overload for Pairwise
atifaziz Jun 15, 2019
9d158e3
Generate Pairwise overload wrapper
atifaziz Jun 15, 2019
87840ab
Merge branch 'master' into vto
atifaziz Oct 30, 2019
1b33fb1
Refresh wrapper extensions
atifaziz Oct 30, 2019
16af855
Update README with Pairwise overload count
atifaziz Oct 30, 2019
96506e7
Tuple-returning overload for EquiZip
Orace Oct 30, 2019
312865b
Fix build.
Orace Oct 30, 2019
0ba21fc
Simplify implementation of EquiZip for tuple-returning
Orace Oct 31, 2019
c1ae035
Update README
Orace Oct 31, 2019
39c5147
Merge pull request atifaziz/MoreLINQ#1 from Orace/vto
atifaziz Oct 31, 2019
cbe745c
Refresh wrapper extensions
atifaziz Oct 31, 2019
2a5ad8a
Tuple-returning overload for ZipLongest
Orace Oct 31, 2019
667e986
Merge pull request atifaziz/MoreLINQ#2 from Orace/vto-ZipLongest
atifaziz Oct 31, 2019
c5aba9d
Refresh wrapper extensions
atifaziz Oct 31, 2019
d726634
Tuple-returning overload for ZipShortest
Orace Oct 31, 2019
8465c2d
Tuple-returning overload for TagFirstLast
Orace Oct 31, 2019
6d183f4
Refresh wrapper extensions
atifaziz Oct 31, 2019
06e660c
Merge pull request atifaziz/MoreLINQ#4 from Orace/vto-ZipShortest
atifaziz Oct 31, 2019
ea0d470
Tuple-returning overload for Lag
Orace Oct 31, 2019
b189c33
Update MoreLinq/TagFirstLast.cs
Orace Oct 31, 2019
b78bdd2
Fix comments
Orace Oct 31, 2019
003e298
Refresh wrapper extensions
Orace Oct 31, 2019
5639dd8
Refresh wrapper extensions
Orace Oct 31, 2019
5035b57
Revert changes on MoreLinq.Test.csproj
Orace Oct 31, 2019
fac3188
Fix comment (again)
Orace Oct 31, 2019
1f77a7f
Refresh wrapper extensions
Orace Oct 31, 2019
d902195
Re-format comment
atifaziz Oct 31, 2019
79bfcf8
Merge pull request atifaziz/MoreLINQ#5 from Orace/vto-TagFirstLast
atifaziz Oct 31, 2019
93ad6be
Update README
Orace Nov 4, 2019
d127398
sourve enumerator wasn't disposed in Lead implementation.
Orace Nov 4, 2019
79db9ad
Refresh wrapper extensions
Orace Nov 4, 2019
39c4014
Add ValueTuple overload for Lead.
Orace Nov 4, 2019
efa6e28
Tuple-returning overload for CountDown
Orace Nov 4, 2019
de9197e
Tuple-returning overload for Cartesian
Orace Nov 4, 2019
1af0647
Fix tuple-returning overload for Cartesian
Orace Nov 4, 2019
1eab72e
Fix build
Orace Nov 4, 2019
383b45c
Typo
Orace Nov 4, 2019
e8dd9ba
Typo
Orace Nov 4, 2019
6a9b4a4
Typo again
Orace Nov 4, 2019
b8843be
Merge pull request atifaziz/MoreLINQ#13 from Orace/vto-CountDown
atifaziz Nov 4, 2019
970f872
There is 14 overload of the Cartesian method.
Orace Nov 5, 2019
c3328fa
Merge pull request atifaziz/MoreLINQ#7 from Orace/vto-Lag2
atifaziz Nov 8, 2019
3f4cc26
Update MoreLinq/Lead.cs
Orace Nov 8, 2019
15e0c1b
Merge pull request atifaziz/MoreLINQ#12 from Orace/vto-Lead
atifaziz Nov 9, 2019
9b4a40b
Merge branch 'master' into vto
atifaziz Jan 17, 2023
da894cb
Merge branch 'master' into vto
atifaziz Oct 21, 2023
ca68c81
Sync "Pairwise" overloads tests
atifaziz Oct 21, 2023
c7d28d2
Merge branch 'vto' into vto-Cartesian
atifaziz Oct 22, 2023
ba17cd6
Fix spacing inconsistency in template
atifaziz Oct 22, 2023
ca50d61
Review template queries
atifaziz Oct 22, 2023
8133e04
Name tuple elements using ordinals
atifaziz Oct 22, 2023
8c9cd8a
Merge pull request #14 from Orace/vto-Cartesian
atifaziz Oct 22, 2023
259c31f
Merge branch 'master' into vto
atifaziz Oct 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 47 additions & 17 deletions MoreLinq.Test/PairwiseTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,30 +20,60 @@ namespace MoreLinq.Test
using NUnit.Framework;

[TestFixture]
public class PairwiseTest
public static class PairwiseTest
{
[Test]
public void PairwiseIsLazy()
public class ReturningTuples
{
_ = new BreakingSequence<object>().Pairwise(BreakingFunc.Of<object, object, int>());
}
[Test]
public void PairwiseIsLazy()
{
_ = new BreakingSequence<object>().Pairwise();
}

[TestCase(0)]
[TestCase(1)]
public void PairwiseWithSequenceShorterThanTwo(int count)
{
var source = Enumerable.Range(0, count);
var result = source.Pairwise(BreakingFunc.Of<int, int, int>());
[TestCase(0)]
[TestCase(1)]
public void PairwiseWithSequenceShorterThanTwo(int count)
{
var source = Enumerable.Range(0, count);
var result = source.Pairwise();

Assert.That(result, Is.Empty);
}

Assert.That(result, Is.Empty);
[Test]
public void PairwiseWideSourceSequence()
{
using var source = new[] { "a", "b", "c", "d" }.AsTestingSequence();
var result = source.Pairwise();
result.AssertSequenceEqual(("a", "b"), ("b", "c"), ("c", "d"));
}
}

[Test]
public void PairwiseWideSourceSequence()
public class ReturningSomeResults
{
using var source = new[] { "a", "b", "c", "d" }.AsTestingSequence();
var result = source.Pairwise((x, y) => x + y);
result.AssertSequenceEqual("ab", "bc", "cd");
[Test]
public void PairwiseIsLazy()
{
_ = new BreakingSequence<object>().Pairwise(BreakingFunc.Of<object, object, int>());
}

[TestCase(0)]
[TestCase(1)]
public void PairwiseWithSequenceShorterThanTwo(int count)
{
var source = Enumerable.Range(0, count);
var result = source.Pairwise(BreakingFunc.Of<int, int, int>());

Assert.That(result, Is.Empty);
}

[Test]
public void PairwiseWideSourceSequence()
{
using var source = new[] { "a", "b", "c", "d" }.AsTestingSequence();
var result = source.Pairwise((x, y) => x + y);
result.AssertSequenceEqual("ab", "bc", "cd");
}
}
}
}
252 changes: 252 additions & 0 deletions MoreLinq/Cartesian.g.cs

Large diffs are not rendered by default.

70 changes: 50 additions & 20 deletions MoreLinq/Cartesian.g.tt
Original file line number Diff line number Diff line change
Expand Up @@ -43,19 +43,20 @@ namespace MoreLinq
{
new[]
{
new { Ordinal = "first" , Arity = "one" },
new { Ordinal = "second" , Arity = "two" },
new { Ordinal = "third" , Arity = "three" },
new { Ordinal = "fourth" , Arity = "four" },
new { Ordinal = "fifth" , Arity = "five" },
new { Ordinal = "sixth" , Arity = "six" },
new { Ordinal = "seventh", Arity = "seven" },
new { Ordinal = "eighth" , Arity = "eight" },
new { Ordinal = "First" , Arity = "one" },
new { Ordinal = "Second" , Arity = "two" },
new { Ordinal = "Third" , Arity = "three" },
new { Ordinal = "Fourth" , Arity = "four" },
new { Ordinal = "Fifth" , Arity = "five" },
new { Ordinal = "Sixth" , Arity = "six" },
new { Ordinal = "Seventh", Arity = "seven" },
new { Ordinal = "Eighth" , Arity = "eight" },
}
}
select args.Select((a, i) => new
{
a.Ordinal,
OrdinalLower = a.Ordinal.ToLowerInvariant(),
a.Arity,
Count = i + 1,
Number = (i + 1).ToString(CultureInfo.InvariantCulture),
Expand All @@ -66,9 +67,8 @@ namespace MoreLinq
select new
{
a.Arity,
Arguments = args.Take(a.Count)
.Select(aa => new { aa.Number, aa.Ordinal })
.ToList(),
Arguments = args.Take(a.Count).ToList(),
TypeParams = string.Join(", ", from aa in args.Take(a.Count) select $"T{aa.Number}")
};

foreach (var o in overloads)
Expand All @@ -81,12 +81,12 @@ namespace MoreLinq
/// </summary>
<# foreach (var arg in o.Arguments) { #>
/// <typeparam name="T<#= arg.Number #>">
/// The type of the elements of <paramref name="<#= arg.Ordinal #>"/>.</typeparam>
/// The type of the elements of <paramref name="<#= arg.OrdinalLower #>"/>.</typeparam>
<# } #>
/// <typeparam name="TResult">
/// The type of the elements of the result sequence.</typeparam>
<# foreach (var arg in o.Arguments) {#>
/// <param name="<#= arg.Ordinal #>">The <#= arg.Ordinal #> sequence of elements.</param>
/// <param name="<#= arg.OrdinalLower #>">The <#= arg.OrdinalLower #> sequence of elements.</param>
<# } #>
/// <param name="resultSelector">A projection function that combines
/// elements from all of the sequences.</param>
Expand All @@ -102,37 +102,67 @@ namespace MoreLinq
/// This method uses deferred execution and stream its results.</para>
/// </remarks>

public static IEnumerable<TResult> Cartesian<<#= string.Join(", ", from x in o.Arguments select "T" + x.Number) #>, TResult>(
public static IEnumerable<TResult> Cartesian<<#= o.TypeParams #>, TResult>(
this <#
foreach (var arg in o.Arguments) { #>
IEnumerable<T<#= arg.Number #>> <#= arg.Ordinal #>,
IEnumerable<T<#= arg.Number #>> <#= arg.OrdinalLower #>,
<#
} #>
Func<<#= string.Join(", ", from x in o.Arguments select "T" + x.Number) #>, TResult> resultSelector)
Func<<#= o.TypeParams #>, TResult> resultSelector)
{
<# foreach (var arg in o.Arguments) { #>
if (<#= arg.Ordinal #> == null) throw new ArgumentNullException(nameof(<#= arg.Ordinal #>));
if (<#= arg.OrdinalLower #> == null) throw new ArgumentNullException(nameof(<#= arg.OrdinalLower #>));
<# } #>
if (resultSelector == null) throw new ArgumentNullException(nameof(resultSelector));

return _(); IEnumerable<TResult> _()
{
<# foreach (var arg in o.Arguments.Skip(1)) { #>
IEnumerable<T<#= arg.Number #>> <#= arg.Ordinal #>Memo;
IEnumerable<T<#= arg.Number #>> <#= arg.OrdinalLower #>Memo;
<# } #>

<# foreach (var arg in o.Arguments.Skip(1)) { #>
using ((<#= arg.Ordinal #>Memo = <#= arg.Ordinal #>.Memoize()) as IDisposable)
using ((<#= arg.OrdinalLower #>Memo = <#= arg.OrdinalLower #>.Memoize()) as IDisposable)
<# } #>
{
foreach (var item1 in first)
<# foreach (var arg in o.Arguments.Skip(1)) { #>
foreach (var item<#= arg.Number #> in <#= arg.Ordinal #>Memo)
foreach (var item<#= arg.Number #> in <#= arg.OrdinalLower #>Memo)
<# } #>
yield return resultSelector(<#= string.Join(", ", from x in o.Arguments select "item" + x.Number) #>);
}
}
}

/// <summary>
/// Returns the Cartesian product of <#= o.Arity #> sequences by enumerating tuples
/// of all possible combinations of one item from each sequence.
/// </summary>
<# foreach (var arg in o.Arguments) { #>
/// <typeparam name="T<#= arg.Number #>">The type of the elements of <paramref name="<#= arg.OrdinalLower #>"/>.</typeparam>
<# } #>
<# foreach (var arg in o.Arguments) {#>
/// <param name="<#= arg.OrdinalLower #>">The <#= arg.OrdinalLower #> sequence of elements.</param>
<# } #>
/// <returns>A sequence of tuples.</returns>
/// <remarks>
/// <para>
/// The method returns items in the same order as a nested foreach
/// loop, but all sequences except for <paramref name="first"/> are
/// cached when iterated over. The cache is then re-used for any
/// subsequent iterations.</para>
/// <para>
/// This method uses deferred execution and stream its results.</para>
/// </remarks>

public static IEnumerable<(<#= string.Join(", ", from a in o.Arguments select $"T{a.Number} {a.Ordinal}") #>)>
Cartesian<<#= o.TypeParams #>>(
this <#= string.Join($",{Environment.NewLine} ",
from arg in o.Arguments
select $"IEnumerable<T{arg.Number}> {arg.OrdinalLower}") #>)
{
return Cartesian(<#= string.Join(", ", from a in o.Arguments select a.OrdinalLower) #>, ValueTuple.Create);
}
<# } #>
}
}
26 changes: 26 additions & 0 deletions MoreLinq/CountDown.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,31 @@ IEnumerable<TResult> IterateSequence()
yield return resultSelector(queue.Dequeue(), queue.Count);
}
}

/// <summary>
/// Provides a countdown counter for a given count of elements at the
/// tail of the sequence where zero always represents the last element,
/// one represents the second-last element, two represents the
/// third-last element and so on.
/// </summary>
/// <typeparam name="T">
/// The type of elements of <paramref name="source"/></typeparam>
/// <param name="source">The source sequence.</param>
/// <param name="count">Count of tail elements of <paramref name="source"/> to count down.</param>
/// <returns>
/// A sequence of tuple with an element from <paramref name="source"/> and its countdown.
/// For elements before the last <paramref name="count"/>, the countdown value is <c>null</c>.
/// </returns>
/// <remarks>
/// This method uses deferred execution semantics and streams its
/// results. At most, <paramref name="count"/> elements of the source
/// sequence may be buffered at any one time unless
/// <paramref name="source"/> is a collection or a list.
/// </remarks>

public static IEnumerable<(T Item, int? CountDown)> CountDown<T>(this IEnumerable<T> source, int count)
{
return source.CountDown(count, ValueTuple.Create);
}
}
}
112 changes: 112 additions & 0 deletions MoreLinq/EquiZip.cs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,118 @@ public static IEnumerable<TResult> EquiZip<T1, T2, T3, T4, TResult>(
return EquiZipImpl(first, second, third, fourth, resultSelector);
}

/// <summary>
/// Returns tuples, where each tuple contains the N-th
/// element from each of the argument sequences. An exception is thrown
/// if the input sequences are of different lengths.
/// </summary>
/// <typeparam name="T1">Type of elements in first sequence</typeparam>
/// <typeparam name="T2">Type of elements in second sequence</typeparam>
/// <param name="first">The first sequence.</param>
/// <param name="second">The second sequence.</param>
/// <returns>
/// A sequence of tuples that contains elements of the two input sequences.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The input sequences are of different lengths.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var numbers = new[] { 1, 2, 3, 4 };
/// var letters = new[] { "A", "B", "C", "D" };
/// var zipped = numbers.EquiZip(letters);
/// ]]></code>
/// The <c>zipped</c> variable, when iterated over, will yield the tuples : (1, A),
/// (2, B), (3, C), (4, D) in turn.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>

public static IEnumerable<(T1, T2)> EquiZip<T1, T2>(this IEnumerable<T1> first, IEnumerable<T2> second)
{
return first.EquiZip(second, ValueTuple.Create);
}

/// <summary>
/// Returns tuples, where each tuple contains the N-th
/// element from each of the argument sequences. An exception is thrown
/// if the input sequences are of different lengths.
/// </summary>
/// <typeparam name="T1">Type of elements in first sequence</typeparam>
/// <typeparam name="T2">Type of elements in second sequence</typeparam>
/// <typeparam name="T3">Type of elements in third sequence</typeparam>
/// <param name="first">The first sequence.</param>
/// <param name="second">The second sequence.</param>
/// <param name="third">The third sequence.</param>
/// <returns>
/// A sequence of tuples that contains elements of the three input sequences.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The input sequences are of different lengths.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var numbers = new[] { 1, 2, 3, 4 };
/// var letters = new[] { "A", "B", "C", "D" };
/// var chars = new[] { 'a', 'b', 'c', 'd' };
/// var zipped = numbers.EquiZip(letters, chars);
/// ]]></code>
/// The <c>zipped</c> variable, when iterated over, will yield the tuples : (1, A, a),
/// (2, B, b), (3, C, c), (4, D, d) in turn.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>

public static IEnumerable<(T1, T2, T3)> EquiZip<T1, T2, T3>(
this IEnumerable<T1> first,
IEnumerable<T2> second, IEnumerable<T3> third)
{
return first.EquiZip(second, third, ValueTuple.Create);
}

/// <summary>
/// Returns tuples, where each tuple contains the N-th
/// element from each of the argument sequences. An exception is thrown
/// if the input sequences are of different lengths.
/// </summary>
/// <typeparam name="T1">Type of elements in first sequence</typeparam>
/// <typeparam name="T2">Type of elements in second sequence</typeparam>
/// <typeparam name="T3">Type of elements in third sequence</typeparam>
/// <typeparam name="T4">Type of elements in fourth sequence</typeparam>
/// <param name="first">The first sequence.</param>
/// <param name="second">The second sequence.</param>
/// <param name="third">The third sequence.</param>
/// <param name="fourth">The fourth sequence.</param>
/// <returns>
/// A sequence of tuples that contains elements of the four input sequences.
/// </returns>
/// <exception cref="InvalidOperationException">
/// The input sequences are of different lengths.
/// </exception>
/// <example>
/// <code><![CDATA[
/// var numbers = new[] { 1, 2, 3, 4 };
/// var letters = new[] { "A", "B", "C", "D" };
/// var chars = new[] { 'a', 'b', 'c', 'd' };
/// var flags = new[] { true, false, true, false };
/// var zipped = numbers.EquiZip(letters, chars, flags);
/// ]]></code>
/// The <c>zipped</c> variable, when iterated over, will yield the tuples : (1, A, a, True),
/// (2, B, b, False), (3, C, c, True), (4, D, d, False) in turn.
/// </example>
/// <remarks>
/// This operator uses deferred execution and streams its results.
/// </remarks>

public static IEnumerable<(T1, T2, T3, T4)> EquiZip<T1, T2, T3, T4>(
this IEnumerable<T1> first,
IEnumerable<T2> second, IEnumerable<T3> third, IEnumerable<T4> fourth)
{
return first.EquiZip(second, third, fourth, ValueTuple.Create);
}

static IEnumerable<TResult> EquiZipImpl<T1, T2, T3, T4, TResult>(
IEnumerable<T1> s1,
IEnumerable<T2> s2,
Expand Down
Loading