Как переписать Single(OrDefault) с помощью LINQ Aggregate Catamorpshim?
Я читал статью, написанную Бартом де Сметом довольно давно: http://community.bartdesmet.net/blogs/bart/archive/2009/11/08/jumping-the-trampoline-in-c-stack-friendly-recursion.aspx
Читателю оставлено в качестве упражнения определение всех других катаморфических операторов в LINQ в терминах оператора Aggregate:
- Простой: (длинный) счетчик, сумма, средняя, минимальная, максимальная
- немного сложнее: все, любое, содержит
- Больше размышлений: первый(OrDefault), последний(OrDefault), одиночный(OrDefault)
Мне удалось использовать совокупный катаморфизм почти для всего, кроме метода расширения Single(). Кроме того, в нескольких случаях, таких как Contains() или First(), нет должного переписывания, потому что я не вижу, как можно остановить Совокупный катаморфизм (всякий раз, когда был найден элемент, соответствующий некоторым условиям) вместо того, чтобы перебирать остальную часть всей данной последовательности.
Подводить итоги:
Переписать метод расширения Single() с помощью Aggregate?
- Как остановить агрегатный катаклизм LINQ, чтобы вернуть элемент, когда элемент был найден, без продолжения итерации остальной части данной последовательности (например, AggregateContains() должен вернуть true для первого элемента, соответствующего значению, и прекратить итерацию).
public static class Program
{
private static void Main()
{
var n = 42;
var numbers = Enumerable.Range(1, n).ToArray();
Console.WriteLine(numbers.Count() == numbers.AggregateCount());
Console.WriteLine(numbers.Count(IsEven) == numbers.AggregateCount(IsEven));
Console.WriteLine(numbers.LongCount() == numbers.AggregateLongCount());
Console.WriteLine(numbers.LongCount(IsEven) == numbers.AggregateLongCount(IsEven));
Console.WriteLine(numbers.Sum() == numbers.AggregateSum());
Console.WriteLine(numbers.Sum(i => i.IsEven() ? i : 0) == numbers.AggregateSum(i => i.IsEven() ? i : 0));
Console.WriteLine(numbers.Average() == numbers.AggregateAverage());
Console.WriteLine(numbers.Average(i => i * 2) == numbers.AggregateAverage(i => i * 2));
Console.WriteLine(numbers.Min() == numbers.AggregateMin());
Console.WriteLine(numbers.Max() == numbers.AggregateMax());
Console.WriteLine(numbers.All(i => i < n + 1) == numbers.AggregateAll(i => i < n + 1));
Console.WriteLine(numbers.Any(IsEven) == numbers.AggregateAny(IsEven));
Console.WriteLine(numbers.Last() == numbers.AggregateLast());
Console.WriteLine(numbers.LastOrDefault() == numbers.AggregateLastOrDefault());
Console.WriteLine(numbers.Last(IsEven) == numbers.AggregateLast(IsEven));
Console.WriteLine(numbers.LastOrDefault(IsEven) == numbers.AggregateLastOrDefault(IsEven));
Console.WriteLine(numbers.First() == numbers.AggregateFirst());
Console.WriteLine(numbers.FirstOrDefault() == numbers.AggregateFirstOrDefault());
Console.WriteLine(numbers.First(IsEven) == numbers.AggregateFirst(IsEven));
Console.WriteLine(numbers.FirstOrDefault(IsEven) == numbers.AggregateFirstOrDefault(IsEven));
Console.ReadKey();
}
private static bool IsEven(this int number)
{
return number % 2 == 0;
}
}
public static class EnumerableAggregateExtensions
{
public static int AggregateCount<T>(this IEnumerable<T> source)
{
return source.Aggregate(0, (r, i) => r + 1);
}
public static int AggregateCount<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source.Aggregate(0, (r, i) => predicate(i) ? r + 1 : r);
}
public static long AggregateLongCount<T>(this IEnumerable<T> source)
{
return source.Aggregate(0L, (r, i) => r + 1);
}
public static long AggregateLongCount<T>(this IEnumerable<T> source, Func<T, bool> predicate)
{
return source.Aggregate(0L, (r, i) => predicate(i) ? r + 1 : r);
}
public static int AggregateSum(this IEnumerable<int> source)
{
return source.Aggregate(0, (r, i) => r + i);
}
public static int AggregateSum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
return source.Aggregate(0, (r, i) => r + selector(i));
}
public static double AggregateAverage(this IEnumerable<int> source)
{
return source.Aggregate(new { Sum = 0, Count = 0 }, (r, i) => new { Sum = r.Sum + i, Count = r.Count + 1 }, r => r.Sum / (double)r.Count);
}
// Could use a nominable mutable type definition to avoid the readonly anonymous type re-allocation:
//public class Int32SumCountHolder
//{
// public int Sum { get; private set; }
// public int Count { get; private set; }
// public double? Average => Count > 0 ? Sum / (double)Count : new double?();
// public Int32SumCountHolder(int sum = 0, int count = 0)
// {
// Sum = sum;
// Count = count;
// }
// public Int32SumCountHolder Increase(int value)
// {
// Sum += value;
// Count ++;
// return this;
// }
//}
public static double AggregateAverage<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
return source.Aggregate(new { Sum = 0, Count = 0 }, (r, i) => new { Sum = r.Sum + selector(i), Count = r.Count + 1 }, r => r.Sum / (double)r.Count);
}
public static TSource AggregateMin<TSource>(this IEnumerable<TSource> source)
{
var comparer = Comparer<TSource>.Default;
return source.Aggregate((r, i) => comparer.Compare(i, r) < 0 ? i : r);
}
public static TResult AggregateMin<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
var comparer = Comparer<TResult>.Default;
return source.Select(selector).Aggregate((r, i) => comparer.Compare(i, r) < 0 ? i : r);
}
public static TSource AggregateMax<TSource>(this IEnumerable<TSource> source)
{
var comparer = Comparer<TSource>.Default;
return source.Aggregate((r, i) => comparer.Compare(i, r) > 0 ? i : r);
}
public static TResult AggregateMax<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, TResult> selector)
{
var comparer = Comparer<TResult>.Default;
return source.Select(selector).Aggregate((r, i) => comparer.Compare(i, r) > 0 ? i : r);
}
public static bool AggregateAll<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return source.Aggregate(true, (r, i) => r && predicate(i));
}
public static bool AggregateAny<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
// How to make the Aggregate call stop whenever the predicate is okay rather than looping over the whole source?
return source.Aggregate(false, (r, i) => r || predicate(i));
}
public static bool AggregateContains<TSource>(this IEnumerable<TSource> source, TSource value)
{
return source.AggregateContains(value, EqualityComparer<TSource>.Default);
}
public static bool AggregateContains<TSource>(this IEnumerable<TSource> source, TSource value, IEqualityComparer<TSource> comparer)
{
// Same as for AggregateAny... how to make it stop earlier?
return source.Aggregate(false, (r, i) => r || comparer.Equals(i, value));
}
public static TSource AggregateFirst<TSource>(this IEnumerable<TSource> source)
{
// Same as for AggregateContains... how to make it stop earlier?
return source.Aggregate(default(Tuple<TSource>), (r, i) => r ?? new Tuple<TSource>(i)).Item1;
}
public static TSource AggregateFirstOrDefault<TSource>(this IEnumerable<TSource> source)
{
var result = source.Aggregate(default(Tuple<TSource>), (r, i) => r ?? new Tuple<TSource>(i));
return result == null ? default(TSource) : result.Item1;
}
public static TSource AggregateFirst<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
// Same as for above...
return source.Aggregate(default(Tuple<TSource>), (r, i) => predicate(i) && r == null ? new Tuple<TSource>(i) : r).Item1;
}
public static TSource AggregateFirstOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
// Same as for above...
var result = source.Aggregate(default(Tuple<TSource>), (r, i) => predicate(i) && r == null ? new Tuple<TSource>(i) : r);
return result == null ? default(TSource) : result.Item1;
}
public static TSource AggregateLast<TSource>(this IEnumerable<TSource> source)
{
return source.Aggregate((r, i) => i);
}
public static TSource AggregateLast<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return source.Aggregate(default(Tuple<TSource>), (r, i) => predicate(i) ? new Tuple<TSource>(i) : r).Item1;
}
public static TSource AggregateLastOrDefault<TSource>(this IEnumerable<TSource> source)
{
return source.Aggregate(default(TSource), (r, i) => i);
}
public static TSource AggregateLastOrDefault<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
return source.Aggregate(default(TSource), (r, i) => predicate(i) ? i : r);
}
}