C#中是否已经有一个函数可以执行"条件Zip"?
即
是否有一个函数允许不同长度的输入并采用谓词来确定何时递增较小的源枚举器,以便看到较大源中的所有元素?
作为一个人为的例子,让我们假设我们有一个素数的枚举和一个整数的枚举(都是升序排序)。我们希望生成一个新的枚举对象,它保存自上一个素数以来的素数和所有整数。
{2, 3, 5, 7, 11}
{1, 2, 3, 4, 5, 6, 7, 8, 9, 10,}
{2, [1]}, {3,[]}, {5, [4]}, {7, [6]}, {11, [8,9,10]}
我的解决方案:
public static IEnumerable<Tuple<T1, IEnumerable<T2>>> ConditionalZip<T1, T2>(
this IEnumerable<T1> src1,
IEnumerable<T2> src2,
Func<T1, T2, bool> check)
{
var list = new List<T2>();
using(var enumerator = src2.GetEnumerator())
{
foreach(var item1 in src1)
{
while(enumerator.MoveNext())
{
var pickedItem = enumerator.Current;
if(check(item1, pickedItem))
{
list.Add(pickedItem);
}
else
{
break;
}
}
var items = list.ToArray();
list.Clear();
yield return new Tuple<T1, IEnumerable<T2>>(item1, items);
}
}
}
它保证两个枚举将只枚举一次。
用法:
var src1 = new int[] { 2, 3, 5, 7, 11 };
var src2 = Enumerable.Range(1, 11);
Func<int, int, bool> predicate = (i1, i2) => i1 > i2;
var result = src1.ConditionalZip(src2, predicate);
这很好。我不认为你会直接在 .net 中找到一个现成的函数,但如果你想要的操作是数学中的标准操作,我相信某处有一个库可以做到这一点。但是,如果您想自己做,则可以使用分组依据。在此特定方案中:
var primes = new List<int> {2, 3, 5, 7, 11};
var numbers = new List<int> {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
var groups =
from number in numbers
group number by primes.First(prime => prime >= number) into gnumber
select new {
prime = gnumber.Key,
numbers = gnumber.Where(n => n != gnumber.Key)
};
这似乎是一个足够简单的解决方案。它将创建一个包含两个成员的匿名类型的枚举对象。您可以将其转换为字典:
var dict = groups.ToDictionary(g => g.prime, g=> g.numbers);
编辑:必须订购素数才能正常工作。
这就是我采用的(丑陋的实现),但只枚举一次枚举。
/// <summary>
/// Merges two sequences by using the specified predicate function to determine when to iterate the second enumerbale.
/// </summary>
///
/// <returns>
/// An <see cref="T:System.Collections.Generic.IEnumerable`1"/> that contains merged elements of two input sequences.
/// </returns>
/// <param name="larger">The first sequence to merge.</param><param name="smaller">The second sequence to merge.</param>
/// <param name="resultSelector">A function that specifies how to merge the elements from the two sequences (a flag is passed into the dunction to notify when elements of the second source are exhausted.</param>
/// <typeparam name="TFirst">The type of the elements of the first input sequence.</typeparam>
/// <typeparam name="TSecond">The type of the elements of the second input sequence.</typeparam>
/// <typeparam name="TResult">The type of the elements of the result sequence.</typeparam>
public static IEnumerable<TResult> ConditionalZip<TFirst, TSecond, TResult>(this IEnumerable<TFirst> larger, IEnumerable<TSecond> smaller, Func<TFirst, TSecond, bool> predicate, Func<TFirst, TSecond, bool, TResult> resultSelector)
{
if (larger == null)
throw new ArgumentNullException("larger");
if (smaller == null)
throw new ArgumentNullException("smaller");
if (resultSelector == null)
throw new ArgumentNullException("resultSelector");
else
return ConditionalZipIterator(larger, smaller, predicate, resultSelector);
}
private static IEnumerable<TResult> ConditionalZipIterator<TFirst, TSecond, TResult>(IEnumerable<TFirst> first, IEnumerable<TSecond> second, Func<TFirst, TSecond, bool> predicate, Func<TFirst, TSecond, bool, TResult> resultSelector)
{
using (IEnumerator<TFirst> enumerator1 = first.GetEnumerator())
{
using (IEnumerator<TSecond> enumerator2 = second.GetEnumerator())
{
if (!enumerator2.MoveNext())
{
secondIsFinished = true;
}
currentSecond = secondIsFinished ? default(TSecond) : enumerator2.Current;
while (enumerator1.MoveNext())
{
while (!secondIsFinished && !predicate(enumerator1.Current, currentSecond))
{
if (!enumerator2.MoveNext())
{
secondIsFinished = true;
}
currentSecond = secondIsFinished ? default(TSecond) : enumerator2.Current;
}
yield return resultSelector(enumerator1.Current, currentSecond, secondIsFinished);
}
}
}
}
使用权
var primes = new int[] {2, 3, 5, 7, 11}.ThrowIfEnumeratedMoreThan(1); var ints = Enumerable.Range(1, 20).ThrowIfEnumeratedMoreThan(1);
var results = ints.ConditionalZip(primes, (i, prime) => i <= prime, (i, prime, isEmpty) => new {i, prime, wasMatched=!isEmpty})
.Where(x => x.wasMatched)
.GroupBy(x => x.prime)
.Select(x => new {Prime = x.Key, Values = x.Where(n => n.i != n.prime).Select(n=>n.i).ToArray()})
.ToArray();