/// <summary> /// Returns a data-producer that will ignore the /// elements from the start of a sequence while a condition /// (involving the elements's index in the sequence) /// is satsified; when the condition fails for an element, /// that element and all subsequent elements are yielded. /// </summary> /// <param name="source">The source data-producer</param> /// <param name="predicate">The condition to skip elements</param> public static IDataProducer <TSource> SkipWhile <TSource>(this IDataProducer <TSource> source, Func <TSource, int, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); DataProducer <TSource> ret = new(); Action completion = () => ret.End(); bool skipping = true; int index = 0; source.DataProduced += value => { skipping = skipping switch { true => predicate(value, index++), _ => skipping }; switch (skipping) { // Note - not an else clause! case false: ret.Produce(value); break; } }; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a data-producer that will yield a specified number of /// contiguous elements from the start of a sequence - i.e. /// "the first <x> elements". /// </summary> /// <param name="source">The source data-producer</param> /// <param name="count">The maximum number of elements to return</param> public static IDataProducer <TSource> Take <TSource>(this IDataProducer <TSource> source, int count) { source.ThrowIfNull("source"); DataProducer <TSource> ret = new(); Action completion = () => ret.End(); Action <TSource> production = null; production = value => { switch (count) { case > 0: ret.Produce(value); count--; break; } switch (count) { case <= 0: source.EndOfData -= completion; source.DataProduced -= production; ret.End(); break; } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a data-producer that yeilds the values from the sequence, or which yields the given /// singleton value if no data is produced. /// </summary> /// <param name="defaultValue">The default value to be yielded if no data is produced.</param> /// <param name="source">The source data-producer.</param> public static IDataProducer <TSource> DefaultIfEmpty <TSource>(this IDataProducer <TSource> source, TSource defaultValue) { source.ThrowIfNull("source"); DataProducer <TSource> ret = new(); bool empty = true; source.DataProduced += value => { empty = false; ret.Produce(value); }; source.EndOfData += () => { switch (empty) { case true: ret.Produce(defaultValue); break; } ret.End(); }; return(ret); }
/// <summary> /// Applies an accumulator function over the values yielded from /// a data-producer. The first value in the seqnence /// is used as the initial accumulator value, and the specified function is used /// to select the result value. If the sequence is empty then /// the default value for TSource is returned. /// </summary> /// <typeparam name="TSource">The type of data yielded by the data-source</typeparam> /// <param name="func">Accumulator function to be applied to each term in the sequence</param> /// <param name="source">The data-source for the values</param> public static IFuture <TSource> Aggregate <TSource> (this IDataProducer <TSource> source, DotNet20.Func <TSource, TSource, TSource> func) { source.ThrowIfNull("source"); func.ThrowIfNull("func"); Future <TSource> ret = new Future <TSource>(); bool first = true; TSource current = default(TSource); source.DataProduced += value => { if (first) { first = false; current = value; } else { current = func(current, value); } }; source.EndOfData += () => ret.Value = current; return(ret); }
private static IOrderedDataProducer <TSource> OrderBy <TSource, TKey>(IDataProducer <TSource> source, DotNet20.Func <TSource, TKey> selector, IComparer <TKey> comparer, bool descending) { source.ThrowIfNull("source"); comparer.ThrowIfNull("comparer"); IComparer <TSource> itemComparer = new ProjectionComparer <TSource, TKey>(selector, comparer); if (descending) { itemComparer = itemComparer.Reverse(); } // first, discard any existing "order by"s by going back to the producer IOrderedDataProducer <TSource> orderedProducer; bool first = true; while ((orderedProducer = source as IOrderedDataProducer <TSource>) != null) { if (first) { // keep the top-most comparer to enforce a balanced sort itemComparer = new LinkedComparer <TSource>(itemComparer, orderedProducer.Comparer); first = false; } source = orderedProducer.BaseProducer; } return(new OrderedDataProducer <TSource>(source, itemComparer)); }
/// <summary> /// Applies an accumulator function over the values yielded from /// a data-producer. The first value in the seqnence /// is used as the initial accumulator value, and the specified function is used /// to select the result value. If the sequence is empty then /// the default value for TSource is returned. /// </summary> /// <typeparam name="TSource">The type of data yielded by the data-source</typeparam> /// <param name="func">Accumulator function to be applied to each term in the sequence</param> /// <param name="source">The data-source for the values</param> public static IFuture <TSource> Aggregate <TSource> (this IDataProducer <TSource> source, Func <TSource, TSource, TSource> func) { source.ThrowIfNull("source"); func.ThrowIfNull("func"); Future <TSource> ret = new(); bool first = true; TSource current = default(TSource); source.DataProduced += value => { switch (first) { case true: first = false; current = value; break; default: current = func(current, value); break; } }; source.EndOfData += () => ret.Value = current; return(ret); }
/// <summary> /// Returns a future to indicate whether the specified value /// is yielded by the data-source. /// </summary> /// <typeparam name="TSource">The type of data to be yielded</typeparam> /// <param name="source">The data-source</param> /// <param name="value">The value to detect from the data-source</param> /// <param name="comparer">The comparer to use to determine equality</param> public static IFuture <bool> Contains <TSource>(this IDataProducer <TSource> source, TSource value, IEqualityComparer <TSource> comparer) { source.ThrowIfNull("source"); comparer.ThrowIfNull("comparer"); return(source.Any(element => comparer.Equals(value, element))); }
/// <summary> /// Returns a future to a single value from a data-source that matches the /// specified condition; an exception /// is thrown if no matching values, or multiple matching values, are encountered. /// </summary> /// <param name="source">The source data-producer.</param> /// <param name="predicate">The condition to be satisfied.</param> /// <exception cref="InvalidOperationException">Zero or multiple matching terms are encountered.</exception> public static IFuture <TSource> Single <TSource>(this IDataProducer <TSource> source, DotNet20.Func <TSource, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); Future <TSource> ret = new Future <TSource>(); TSource output = default(TSource); bool gotValue = false; source.DataProduced += value => { if (predicate(value)) { if (gotValue) { throw new InvalidOperationException("More than one element in source data"); } output = value; gotValue = true; } }; source.EndOfData += () => { if (!gotValue) { throw new InvalidOperationException("No elements in source data"); } ret.Value = output; }; return(ret); }
/// <summary> /// Returns a future to the element at the given position in the sequence, /// or the default-value if the specified index is never reached /// </summary> /// <param name="source">The data-producer</param> /// <param name="index">The index of the desired trem in the sequence</param> /// <exception cref="ArgumentOutOfRangeException">If the specified index is negative</exception> public static IFuture <TSource> ElementAtOrDefault <TSource>(this IDataProducer <TSource> source, int index) { source.ThrowIfNull("source"); switch (index) { case < 0: throw new ArgumentOutOfRangeException("index"); } Future <TSource> ret = new(); Action completion = () => ret.Value = default(TSource); Action <TSource> production = null; production = value => { switch (index) { case 0: ret.Value = value; source.DataProduced -= production; source.EndOfData -= completion; break; default: index--; break; } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a data-producer that will yield /// elements a sequence as long as a condition /// (involving the element's index in the sequence) /// is satsified; when the condition fails for an element, /// that element and all subsequent elements are ignored. /// </summary> /// <param name="source">The source data-producer</param> /// <param name="predicate">The condition to yield elements</param> public static IDataProducer <TSource> TakeWhile <TSource>(this IDataProducer <TSource> source, DotNet20.Func <TSource, int, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); DataProducer <TSource> ret = new DataProducer <TSource>(); Action completion = () => ret.End(); Action <TSource> production = null; int index = 0; production = value => { if (!predicate(value, index++)) { ret.End(); source.DataProduced -= production; source.EndOfData -= completion; } else { ret.Produce(value); } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a data-producer that will ignore the /// elements from the start of a sequence while a condition /// (involving the elements's index in the sequence) /// is satsified; when the condition fails for an element, /// that element and all subsequent elements are yielded. /// </summary> /// <param name="source">The source data-producer</param> /// <param name="predicate">The condition to skip elements</param> public static IDataProducer <TSource> SkipWhile <TSource>(this IDataProducer <TSource> source, DotNet20.Func <TSource, int, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); DataProducer <TSource> ret = new DataProducer <TSource>(); Action completion = () => ret.End(); bool skipping = true; int index = 0; source.DataProduced += value => { if (skipping) { skipping = predicate(value, index++); } // Note - not an else clause! if (!skipping) { ret.Produce(value); } }; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a future to the element at the given position in the sequence, /// or the default-value if the specified index is never reached /// </summary> /// <param name="source">The data-producer</param> /// <param name="index">The index of the desired trem in the sequence</param> /// <exception cref="ArgumentOutOfRangeException">If the specified index is negative</exception> public static IFuture <TSource> ElementAtOrDefault <TSource>(this IDataProducer <TSource> source, int index) { source.ThrowIfNull("source"); if (index < 0) { throw new ArgumentOutOfRangeException("index"); } Future <TSource> ret = new Future <TSource>(); Action completion = () => ret.Value = default(TSource); Action <TSource> production = null; production = value => { if (index == 0) { ret.Value = value; source.DataProduced -= production; source.EndOfData -= completion; } else { index--; } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a data-producer that will yield a specified number of /// contiguous elements from the start of a sequence - i.e. /// "the first <x> elements". /// </summary> /// <param name="source">The source data-producer</param> /// <param name="count">The maximum number of elements to return</param> public static IDataProducer <TSource> Take <TSource>(this IDataProducer <TSource> source, int count) { source.ThrowIfNull("source"); DataProducer <TSource> ret = new DataProducer <TSource>(); Action completion = () => ret.End(); Action <TSource> production = null; production = value => { if (count > 0) { ret.Produce(value); count--; } if (count <= 0) { source.EndOfData -= completion; source.DataProduced -= production; ret.End(); } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns the last element in a sequence that satisfies a specified condition, as a future value. /// </summary> /// <typeparam name="TSource">The type of the elements of source.</typeparam> /// <param name="source">The sequence to an element from.</param> /// <param name="predicate">A function to test each element for a condition.</param> /// <returns>The last element in the specified sequence that passes the test in /// the specified predicate function, as a future value. /// The actual value can only be retrieved after the source has indicated the end /// of its data. /// </returns> public static IFuture <TSource> Last <TSource>(this IDataProducer <TSource> source, Func <TSource, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); Future <TSource> ret = new(); bool gotData = false; TSource prev = default(TSource); source.DataProduced += value => { if (predicate(value)) { prev = value; gotData = true; } }; source.EndOfData += () => { ret.Value = gotData switch { false => throw new InvalidOperationException("Sequence is empty"), _ => prev }; }; return(ret); }
/// <summary> /// Returns a future to a single value from a data-source that matches the /// specified condition, or the default value if no matching values /// are encountered. An exception /// is thrown if multiple matching values are encountered. /// </summary> /// <param name="source">The source data-producer.</param> /// <param name="predicate">The condition to be satisfied.</param> /// <exception cref="InvalidOperationException">Multiple matching terms are encountered.</exception> public static IFuture <TSource> SingleOrDefault <TSource>(this IDataProducer <TSource> source, Func <TSource, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); Future <TSource> ret = new(); TSource output = default(TSource); bool gotValue = false; source.DataProduced += value => { if (predicate(value)) { switch (gotValue) { case true: throw new InvalidOperationException("More than one element in source data"); default: output = value; gotValue = true; break; } } }; source.EndOfData += () => { ret.Value = output; }; return(ret); }
/// <summary> /// Returns a future to the average of a sequence of values that are /// obtained by taking a transform of the input sequence /// </summary> /// <remarks>Null values are removed from the average</remarks> public static IFuture <TResult> Average <TSource, TResult>(this IDataProducer <TSource> source, Func <TSource, TResult> selector) { source.ThrowIfNull("source"); selector.ThrowIfNull("selector"); Future <TResult> ret = new Future <TResult>(); TResult sum = Operator <TResult> .Zero; int count = 0; // should this be long? Would demand a Operator.DivideInt64 source.DataProduced += item => { if (Operator.AddIfNotNull(ref sum, selector(item))) { count++; } }; source.EndOfData += () => { if (count == 0) { // check if Nullable<T> by seeing if default(T) is // nullable; if so, return null; otherwise, throw sum = default(TResult); if (sum != null) { throw new InvalidOperationException("Cannot perform non-nullable average over an empty series"); } ret.Value = sum; // null } else { ret.Value = Operator.DivideInt32(sum, count); } }; return(ret); }
/// <summary> /// Returns a future that indicates whether any suitable values are /// yielded by the data-producer. The future will return false /// for an empty sequence or one with no matching values, or true for a sequence with matching values. /// </summary> /// <param name="source">The data-producer to be monitored.</param> /// <param name="predicate">The condition that must be satisfied.</param> public static IFuture <bool> Any <TSource>(this IDataProducer <TSource> source, Func <TSource, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); Future <bool> ret = new(); Action <TSource> production = null; Action completion = () => ret.Value = false; production = value => { if (predicate(value)) { ret.Value = true; source.DataProduced -= production; source.EndOfData -= completion; } }; source.DataProduced += production; source.EndOfData += completion; return(ret); }
/// <summary> /// Returns a future to the minumum of a sequence of values that are /// obtained by taking a transform of the input sequence, using the default comparer /// </summary> /// <remarks>Null values are removed from the minimum</remarks> public static IFuture <TResult> Min <TSource, TResult> (this IDataProducer <TSource> source, DotNet20.Func <TSource, TResult> selector) { source.ThrowIfNull("source"); selector.ThrowIfNull("selector"); return(source.Select(selector).Min()); }
/// <summary> /// Converts an IDataProducer into a list. An empty list is returned immediately, /// and any results produced are added to it. /// </summary> /// <remarks>This will force all values to be buffered</remarks> public static List <TSource> ToList <TSource>(this IDataProducer <TSource> source) { source.ThrowIfNull("source"); List <TSource> list = new List <TSource>(); source.DataProduced += value => list.Add(value); return(list); }
/// <summary> /// Returns the number of elements in a sequence, as a future value. /// </summary> /// <typeparam name="TSource">The type of the elements of source.</typeparam> /// <param name="source">A sequence that contains elements to be counted.</param> /// <returns>The number of elements in the input sequence, as a future value. /// The actual count can only be retrieved after the source has indicated the end /// of its data. /// </returns> public static IFuture <int> Count <TSource>(this IDataProducer <TSource> source) { source.ThrowIfNull("source"); Future <int> ret = new(); int count = 0; source.DataProduced += t => count++; source.EndOfData += () => ret.Value = count; return(ret); }
/// <summary> /// Converts an IDataProducer into a future array. /// </summary> /// <remarks>This will force all values to be buffered</remarks> public static IFuture <TSource[]> ToFutureArray <TSource>(this IDataProducer <TSource> source) { source.ThrowIfNull("source"); Future <TSource[]> ret = new Future <TSource[]>(); List <TSource> list = source.ToList(); source.EndOfData += () => ret.Value = list.ToArray(); return(ret); }
/// <summary> /// Create a new OrderedDataProducer /// </summary> /// <param name="baseProducer">The base source which will supply data</param> /// <param name="comparer">The comparer to use when sorting the data (once complete)</param> public OrderedDataProducer( IDataProducer <T> baseProducer, IComparer <T> comparer) { baseProducer.ThrowIfNull("baseProducer"); BaseProducer = baseProducer; Comparer = comparer ?? Comparer <T> .Default; baseProducer.DataProduced += new Action <T>(OriginalDataProduced); baseProducer.EndOfData += new Action(EndOfOriginalData); }
/// <summary> /// Create a new OrderedDataProducer /// </summary> /// <param name="baseProducer">The base source which will supply data</param> /// <param name="comparer">The comparer to use when sorting the data (once complete)</param> public OrderedDataProducer( IDataProducer <T> baseProducer, IComparer <T> comparer) { baseProducer.ThrowIfNull("baseProducer"); this.baseProducer = baseProducer; this.comparer = comparer ?? Comparer <T> .Default; baseProducer.DataProduced += OriginalDataProduced; baseProducer.EndOfData += EndOfOriginalData; }
/// <summary> /// Returns a projection on the data-producer, using a transformation /// (involving the elements's index in the sequence) to /// map each element into a new form. /// </summary> /// <typeparam name="TSource">The source type</typeparam> /// <typeparam name="TResult">The projected type</typeparam> /// <param name="source">The source data-producer</param> /// <param name="projection">The transformation to apply to each element.</param> public static IDataProducer <TResult> Select <TSource, TResult>(this IDataProducer <TSource> source, DotNet20.Func <TSource, int, TResult> projection) { source.ThrowIfNull("source"); projection.ThrowIfNull("projection"); DataProducer <TResult> ret = new DataProducer <TResult>(); int index = 0; source.DataProduced += value => ret.Produce(projection(value, index++)); source.EndOfData += () => ret.End(); return(ret); }
/// <summary> /// Converts an IDataProducer into an IFuture[IEnumerable]. The results /// are buffered in memory (as a list), so be warned that this loses the "streaming" /// nature of most of the IDataProducer extension methods. The "future" nature of /// the result ensures that all results are produced before the enumeration can take /// place. /// </summary> /// <remarks>This will force all values to be buffered</remarks> public static IFuture <IEnumerable <TSource> > AsFutureEnumerable <TSource>(this IDataProducer <TSource> source) { source.ThrowIfNull("source"); Future <IEnumerable <TSource> > ret = new Future <IEnumerable <TSource> >(); List <TSource> list = new List <TSource>(); source.DataProduced += value => list.Add(value); source.EndOfData += () => ret.Value = list; return(ret); }
/// <summary> /// Converts an IDataProducer into a dictionary. /// </summary> /// <param name="elementSelector">A transform function to produce a result element value from each element.</param> /// <param name="keySelector">A function to extract a key from each element.</param> /// <param name="keyComparer">Used to compare keys.</param> /// <param name="source">The data source.</param> /// <remarks>This will force all values to be buffered</remarks> public static IDictionary <TKey, TElement> ToDictionary <TSource, TKey, TElement>( this IDataProducer <TSource> source, Func <TSource, TKey> keySelector, Func <TSource, TElement> elementSelector, IEqualityComparer <TKey> keyComparer) { source.ThrowIfNull("source"); keySelector.ThrowIfNull("keySelector"); elementSelector.ThrowIfNull("elementSelector"); keyComparer.ThrowIfNull("keyComparer"); Dictionary <TKey, TElement> dict = new Dictionary <TKey, TElement>(keyComparer); source.DataProduced += t => dict.Add(keySelector(t), elementSelector(t)); return(dict); }
/// <summary> /// Returns a future to the sum of a sequence of values that are /// obtained by taking a transform of the input sequence /// </summary> /// <remarks>Null values are removed from the sum</remarks> public static IFuture <TResult> Sum <TSource, TResult>(this IDataProducer <TSource> source, Func <TSource, TResult> selector) { source.ThrowIfNull("source"); selector.ThrowIfNull("selector"); Future <TResult> ret = new Future <TResult>(); TResult sum = Operator <TResult> .Zero; source.DataProduced += item => { Operator.AddIfNotNull(ref sum, selector(item)); }; source.EndOfData += () => ret.Value = sum; return(ret); }
/// <summary> /// Converts an IDataProducer into a lookup. /// </summary> /// <param name="elementSelector">A transform function to produce a result element value from each element.</param> /// <param name="keySelector">A function to extract a key from each element.</param> /// <param name="keyComparer">Used to compare keys.</param> /// <param name="source">The data source.</param> /// <remarks>This will force all values to be buffered</remarks> public static ILookup <TKey, TElement> ToLookup <TSource, TKey, TElement>( this IDataProducer <TSource> source, Func <TSource, TKey> keySelector, Func <TSource, TElement> elementSelector, IEqualityComparer <TKey> keyComparer) { source.ThrowIfNull("source"); keySelector.ThrowIfNull("keySelector"); elementSelector.ThrowIfNull("elementSelector"); keyComparer.ThrowIfNull("keyComparer"); EditableLookup <TKey, TElement> lookup = new EditableLookup <TKey, TElement>(keyComparer); source.DataProduced += t => lookup.Add(keySelector(t), elementSelector(t)); source.EndOfData += () => lookup.TrimExcess(); return(lookup); }
/// <summary> /// Applies an accumulator function over the values yielded from /// a data-producer, performing a transformation on the final /// accululated value. The specified seed value /// is used as the initial accumulator value, and the specified function is used /// to select the result value /// </summary> /// <typeparam name="TSource">The type of data yielded by the data-source</typeparam> /// <typeparam name="TResult">The final result type (after the accumulator has been transformed)</typeparam> /// <typeparam name="TAccumulate">The type to be used for the accumulator</typeparam> /// <param name="func">Accumulator function to be applied to each term in the sequence</param> /// <param name="resultSelector">Transformation to apply to the final /// accumulated value to produce the result</param> /// <param name="seed">The initial value for the accumulator</param> /// <param name="source">The data-source for the values</param> public static IFuture <TResult> Aggregate <TSource, TAccumulate, TResult> (this IDataProducer <TSource> source, TAccumulate seed, DotNet20.Func <TAccumulate, TSource, TAccumulate> func, DotNet20.Func <TAccumulate, TResult> resultSelector) { source.ThrowIfNull("source"); func.ThrowIfNull("func"); resultSelector.ThrowIfNull("resultSelector"); Future <TResult> result = new Future <TResult>(); TAccumulate current = seed; source.DataProduced += value => current = func(current, value); source.EndOfData += () => result.Value = resultSelector(current); return(result); }
/// <summary> /// Returns the number of elements in the specified sequence satisfy a condition, /// as a future value. /// </summary> /// <typeparam name="TSource">The type of the elements of source.</typeparam> /// <param name="source">A sequence that contains elements to be tested and counted.</param> /// <param name="predicate">A function to test each element for a condition.</param> /// <returns>A number that represents how many elements in the sequence satisfy /// the condition in the predicate function, as a future value. /// The actual count can only be retrieved after the source has indicated the end /// of its data. /// </returns> public static IFuture <int> Count <TSource>(this IDataProducer <TSource> source, Func <TSource, bool> predicate) { source.ThrowIfNull("source"); predicate.ThrowIfNull("predicate"); Future <int> ret = new(); int count = 0; source.DataProduced += t => { if (predicate(t)) { count++; } }; source.EndOfData += () => ret.Value = count; return(ret); }