Created
May 19, 2022 17:42
-
-
Save psmay/3aa6fcadf4935f63ec44f6c3eb377971 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
namespace PSMay.Experiments.MoreZipGist | |
{ | |
// NB: This is a work in progress. It's not fully tested, but it might be good for an idea or two. | |
// | |
// To the extent possible under law, the author(s) have dedicated all copyright and related and | |
// neighboring rights to this software to the public domain worldwide.This software is | |
// distributed without any warranty. | |
// | |
// You should have received a copy of the CC0 Public Domain Dedication along with this | |
// software.If not, see http://creativecommons.org/publicdomain/zero/1.0/. | |
public static class EnumerableExtensions | |
{ | |
/// <summary> | |
/// Produces corresponding elements from two sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<(bool HasFirstValue, TFirst FirstValue, bool HasSecondValue, TSecond SecondValue)> LongZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second) | |
{ | |
if (first is null) | |
{ | |
throw new ArgumentNullException(nameof(first)); | |
} | |
if (second is null) | |
{ | |
throw new ArgumentNullException(nameof(second)); | |
} | |
var extendedFirst = Extended(first); | |
var extendedSecond = Extended(second); | |
return extendedFirst | |
.Zip(extendedSecond, (xFirst, xSecond) => (HasFirstValue: xFirst.HasValue, xFirst.Value, HasSecondValue: xSecond.HasValue, xSecond.Value)) | |
.TakeWhile(x => x.HasFirstValue || x.HasSecondValue); | |
IEnumerable<(bool HasValue, T Value)> Extended<T>(IEnumerable<T> source) | |
{ | |
foreach (var x in source) | |
{ | |
yield return (true, x); | |
} | |
while (true) | |
{ | |
yield return (false, default(T)); | |
} | |
} | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<(bool HasFirstValue, TFirst FirstValue, bool HasSecondValue, TSecond SecondValue, bool HasThirdValue, TThird ThirdValue)> LongZip<TFirst, TSecond, TThird>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third) | |
{ | |
if (first is null) | |
{ | |
throw new ArgumentNullException(nameof(first)); | |
} | |
if (second is null) | |
{ | |
throw new ArgumentNullException(nameof(second)); | |
} | |
if (third is null) | |
{ | |
throw new ArgumentNullException(nameof(third)); | |
} | |
var left = first.LongZip(second); | |
return left.LongZip(third, (hasLeftValue, leftValue, hasThirdValue, thirdValue) => ( | |
// NB: This exploits the fact that the default value of the value tuple is (false, default) and not null | |
leftValue.HasFirstValue, | |
leftValue.FirstValue, | |
leftValue.HasSecondValue, | |
leftValue.SecondValue, | |
HasThirdValue: hasThirdValue, | |
ThirdValue: thirdValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from two sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<TResult> LongZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<bool, TFirst, bool, TSecond, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.LongZip(second).Select(x => resultSelector(x.HasFirstValue, x.FirstValue, x.HasSecondValue, x.SecondValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<TResult> LongZip<TFirst, TSecond, TThird, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third, Func<bool, TFirst, bool, TSecond, bool, TThird, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.LongZip(second, third).Select(x => resultSelector(x.HasFirstValue, x.FirstValue, x.HasSecondValue, x.SecondValue, x.HasThirdValue, x.ThirdValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from two sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<TResult> LongZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<int, bool, TFirst, bool, TSecond, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.LongZip(second).Select((x, i) => resultSelector(i, x.HasFirstValue, x.FirstValue, x.HasSecondValue, x.SecondValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, continuing until the longest | |
/// sequence is exhausted. | |
/// </summary> | |
public static IEnumerable<TResult> LongZip<TFirst, TSecond, TThird, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third, Func<int, bool, TFirst, bool, TSecond, bool, TThird, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.LongZip(second, third).Select((x, i) => resultSelector(i, x.HasFirstValue, x.FirstValue, x.HasSecondValue, x.SecondValue, x.HasThirdValue, x.ThirdValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from two sequences, terminating successfully when both | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the other. | |
/// </summary> | |
public static IEnumerable<(TFirst FirstValue, TSecond SecondValue)> SameLengthZip<TFirst, TSecond>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second) | |
{ | |
return first.LongZip(second, (hasFirstValue, firstValue, hasSecondValue, secondValue) => | |
{ | |
if (hasFirstValue && hasSecondValue) | |
{ | |
return (firstValue, secondValue); | |
} | |
else if (!hasFirstValue) | |
{ | |
throw new InvalidOperationException("The first sequence is shorter than the second sequence."); | |
} | |
else | |
{ | |
throw new InvalidOperationException("The second sequence is shorter than the first sequence."); | |
} | |
}); | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, terminating successfully when all | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the others. | |
/// </summary> | |
public static IEnumerable<(TFirst FirstValue, TSecond SecondValue, TThird ThirdValue)> SameLengthZip<TFirst, TSecond, TThird>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third) | |
{ | |
return first.LongZip(second, third, (hasFirstValue, firstValue, hasSecondValue, secondValue, hasThirdValue, thirdValue) => | |
{ | |
if (hasFirstValue && hasSecondValue && hasThirdValue) | |
{ | |
return (firstValue, secondValue, thirdValue); | |
} | |
var sequencesThatEnded = (new[] { | |
hasFirstValue ? null : "first", | |
hasSecondValue ? null : "second", | |
hasThirdValue ? null : "third" | |
}) | |
.Where(x => x != null) | |
.ToArray(); | |
switch (sequencesThatEnded.Length) | |
{ | |
case 1: | |
throw new InvalidOperationException($"The {sequencesThatEnded[0]} sequence is shorter than the other sequences."); | |
case 2: | |
throw new InvalidOperationException($"The {sequencesThatEnded[0]} and {sequencesThatEnded[1]} sequences are shorter than the other sequence."); | |
default: | |
throw new Exception("Unrecognized state."); | |
} | |
}); | |
} | |
/// <summary> | |
/// Produces corresponding elements from two sequences, terminating successfully when both | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the other. | |
/// </summary> | |
public static IEnumerable<TResult> SameLengthZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.SameLengthZip(second).Select(x => resultSelector(x.FirstValue, x.SecondValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, terminating successfully when both | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the others. | |
/// </summary> | |
public static IEnumerable<TResult> SameLengthZip<TFirst, TSecond, TThird, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third, Func<TFirst, TSecond, TThird, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.SameLengthZip(second, third).Select(x => resultSelector(x.FirstValue, x.SecondValue, x.ThirdValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from two sequences, terminating successfully when both | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the other. | |
/// </summary> | |
public static IEnumerable<TResult> SameLengthZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<int, TFirst, TSecond, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.SameLengthZip(second).Select((x, i) => resultSelector(i, x.FirstValue, x.SecondValue)); | |
} | |
/// <summary> | |
/// Produces corresponding elements from three sequences, terminating successfully when both | |
/// sequences contained the same number of elements or throwing if one sequence ends before | |
/// the other. | |
/// </summary> | |
public static IEnumerable<TResult> SameLengthZip<TFirst, TSecond, TThird, TResult>(this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third, Func<int, TFirst, TSecond, TThird, TResult> resultSelector) | |
{ | |
if (resultSelector is null) | |
{ | |
throw new ArgumentNullException(nameof(resultSelector)); | |
} | |
return first.SameLengthZip(second, third).Select((x, i) => resultSelector(i, x.FirstValue, x.SecondValue, x.ThirdValue)); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment