Skip to content

Instantly share code, notes, and snippets.

@psmay
Created May 19, 2022 17:42
Show Gist options
  • Save psmay/3aa6fcadf4935f63ec44f6c3eb377971 to your computer and use it in GitHub Desktop.
Save psmay/3aa6fcadf4935f63ec44f6c3eb377971 to your computer and use it in GitHub Desktop.
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