You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
/// <summary>/// Methods to extend the DynamicData library./// </summary>publicstaticclassDynamicDataExtensions{/// <summary>/// Performs a maximum operation on the source./// </summary>/// <param name="source">The source.</param>/// <param name="keySelector">The key selector for the object to get the maximum for.</param>/// <typeparam name="TObject">The object type to get the maximum for.</typeparam>/// <typeparam name="TKey">The key selector to determine the maximum.</typeparam>/// <returns>The observable returning the maximum object.</returns>[MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticIObservable<TObject>MaximumBy<TObject,TKey>(thisIObservable<IChangeSet<TObject>>source,Func<TObject,TKey>keySelector)whereTObject:notnull{return source.MaximumBy(keySelector,null);}/// <summary>/// Alternative implementation of the <see cref="MaxEx.Maximum{TObject,TResult}"/> method, which/// uses a comparer for the object itself./// </summary>/// <param name="source">The source.</param>/// <param name="comparer">The comparer for the source object.</param>/// <typeparam name="TObject">The object type to get the maximum for.</typeparam>/// <returns>The observable returning the maximum object.</returns>[MethodImpl(MethodImplOptions.AggressiveInlining)]publicstaticIObservable<TObject>Maximum<TObject>(thisIObservable<IChangeSet<TObject>>source,IComparer<TObject>?comparer)whereTObject:notnull{return source.MaximumBy(static x => x, comparer);}/// <summary>/// Performs a maximum operation on the source using both a key selector and comparer./// </summary>/// <param name="source">The source.</param>/// <param name="keySelector">The key selector for the object to get the maximum for.</param>/// <param name="comparer">The comparer for the source object.</param>/// <typeparam name="TObject">The object type to get the maximum for.</typeparam>/// <typeparam name="TKey">The key selector to determine the maximum.</typeparam>/// <returns>The observable returning the maximum object.</returns>publicstaticIObservable<TObject>MaximumBy<TObject,TKey>(thisIObservable<IChangeSet<TObject>>source,Func<TObject,TKey>keySelector,IComparer<TKey>?comparer)whereTObject:notnull{comparer ??= Comparer<TKey>.Default;return source
.ToChangesAndCollection().Scan(AggregationState<TObject,TKey>.Empty,(state,latest)=>{varcurrent= state;varrequiresReset=false;usingvarchangesEnumerator= latest.Changes.GetEnumerator();if(changesEnumerator.MoveNext()){do{varchange= changesEnumerator.Current;varvalue= keySelector(change.Item);if(change.Type == AggregateType.Add){// Set a new current item, when the current item was not set,// or if the comparer indicates a new maximum value.if(!current.IsSet || comparer.Compare(value, current.Key)>0){current=newAggregationState<TObject,TKey>(change.Item, value);}}else{// check whether the max / min has been removed. If so we need to look// up the latest from the underlying collectionif(!current.IsSet){// Value was not set, but we have a remove change. This means that the// something went wrong???continue;}if(comparer.Compare(value, current.Key)!=0){continue;}requiresReset=true;break;}}while(changesEnumerator.MoveNext());}else{// An empty enumeration occurs when <c>AutoRefresh</c> is used and the// underlying item has been changed.requiresReset=true;}// Do we need to process the whole collection?// This may happen due to a refresh or a removal of the max item.if(requiresReset){varcollection= latest.Collection;if(collection.Count ==0){current=AggregationState<TObject,TKey>.Empty;}else{varfound= collection.MaxBy(keySelector);current= found isnull?AggregationState<TObject,TKey>.Empty:newAggregationState<TObject,TKey>(found, keySelector(found));}}returncurrent;}).Where(x => x.IsSet).Select(x => x.Current!);}/// <summary>/// Copied from the original DynamicData source code./// </summary>/// <param name="source"></param>/// <typeparam name="TObject"></typeparam>/// <returns></returns>privatestaticIObservable<ChangesAndCollection<TObject>>ToChangesAndCollection<TObject>(thisIObservable<IChangeSet<TObject>>source)whereTObject:notnull{return source.Publish(shared =>{varchanges= shared.ForAggregation();vardata= shared.ToCollection();return data.Zip(changes,(d,c)=>newChangesAndCollection<TObject>(c, d));});}/// <summary>/// Contains the current maximum value and its key./// </summary>/// <typeparam name="TObject"></typeparam>/// <typeparam name="TKey"></typeparam>privatesealedclassAggregationState<TObject,TKey>whereTObject:notnull{/// <summary>/// Gets an empty instance of the <see cref="AggregationState{TObject, TKey}"/> class./// </summary>publicstaticAggregationState<TObject,TKey> Empty {get;}=new();/// <summary>/// Initializes a new instance of the <see cref="AggregationState{TObject, TKey}"/> class./// </summary>privateAggregationState(): this(false,default,default){}/// <summary>/// Initializes a new instance of the <see cref="AggregationState{TObject, TKey}"/> class./// </summary>/// <param name="current">The current value.</param>/// <param name="key">The key of the current value.</param>publicAggregationState(TObjectcurrent,TKeykey): this(true, current, key){}/// <summary>/// Initializes a new instance of the <see cref="AggregationState{TObject, TKey}"/> class./// </summary>/// <param name="isSet">Determines whether the current value is set.</param>/// <param name="current">The current value.</param>/// <param name="key">The key of the current value.</param>privateAggregationState(boolisSet,TObject?current,TKey?key){IsSet=isSet;Current=current;Key=key;}/// <summary>/// Gets a value indicating whether this instance is set to a non-null value and key./// </summary>[MemberNotNullWhen(true, nameof(Current), nameof(Key))]publicboolIsSet{get;}/// <summary>/// Gets the value of the current maximum item./// </summary>publicTObject?Current{get;}/// <summary>/// Gets the key of the current maximum item./// </summary>publicTKey?Key{get;}}/// <summary>/// Copied from the original DynamicData source code./// </summary>/// <param name="changes"></param>/// <param name="collection"></param>/// <typeparam name="T"></typeparam>privatesealedclass ChangesAndCollection<T>(IAggregateChangeSet<T> changes,IReadOnlyCollection<T> collection){publicIAggregateChangeSet<T> Changes => changes;publicIReadOnlyCollection<T> Collection => collection;}}
Considerations
I was looking at the Maximum implementation, but ran into two problems:
I was unable to specify a comparer
I was unable to get the maximum based on a property of the source object
The text was updated successfully, but these errors were encountered:
You can get equivalent functionality by combining .Transform() and then .Maximum(), but the ability to specify a comparer would definitely be a good enhancement, and a .MaximumBy() operator with a dedicated implementation would likely provide better performance than .Transform().Maximum().
If you've got a working implementation, I think we'd happily take a PR. Main thing it looks like you'd be missing is tests.
Describe the functionality desired 🐞
The library has a
Maximum
operator, but noMaximumBy
, which allows the aggregation of aTObject
using a key selector.The steps the functionality will provide
Example
Usage
Test object
Extension class
Considerations
I was looking at the
Maximum
implementation, but ran into two problems:The text was updated successfully, but these errors were encountered: