Example #1
0
        /// <summary>
        /// Applies a key-generating function to each element of a sequence and returns a sequence of
        /// unique keys and their number of occurrences in the original sequence.
        /// An additional argument specifies a comparer to use for testing equivalence of keys.
        /// </summary>
        /// <typeparam name="TSource">Type of the elements of the source sequence.</typeparam>
        /// <typeparam name="TKey">Type of the projected element.</typeparam>
        /// <param name="source">Source sequence.</param>
        /// <param name="keySelector">Function that transforms each item of source sequence into a key to be compared against the others.</param>
        /// <param name="comparer">The equality comparer to use to determine whether or not keys are equal.
        /// If null, the default equality comparer for <typeparamref name="TSource"/> is used.</param>
        /// <returns>A sequence of unique keys and their number of occurrences in the original sequence.</returns>

        public static IEnumerable <KeyValuePair <TKey, int> > CountBy <TSource, TKey>(this IEnumerable <TSource> source, Func <TSource, TKey> keySelector, IEqualityComparer <TKey>?comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException(nameof(keySelector));
            }

            return(_()); IEnumerable <KeyValuePair <TKey, int> > _()
            {
                List <TKey> keys;
                List <int>  counts;

                // Avoid the temptation to inline the Loop method, which
                // exists solely to separate the scope & lifetimes of the
                // locals needed for the actual looping of the source &
                // production of the results (that happens once at the start
                // of iteration) from those needed to simply yield the
                // results. It is harder to reason about the lifetimes (if the
                // code is inlined) with respect to how the compiler will
                // rewrite the iterator code as a state machine. For
                // background, see:
                // http://blog.stephencleary.com/2010/02/q-should-i-set-variables-to-null-to.html

                Loop(comparer ?? EqualityComparer <TKey> .Default);

                for (var i = 0; i < keys.Count; i++)
                {
                    yield return(new KeyValuePair <TKey, int>(keys[i], counts[i]));
                }

                void Loop(IEqualityComparer <TKey> cmp)
                {
                    var dic = new Collections.Dictionary <TKey, int>(cmp);

                    keys   = new List <TKey>();
                    counts = new List <int>();
                    (bool, TKey)prevKey = default;
                    var index = 0;

                    foreach (var item in source)
                    {
                        var key = keySelector(item);

                        if (// key same as the previous? then re-use the index
                            prevKey is (true, {} pk) &&
                            cmp.GetHashCode(pk) == cmp.GetHashCode(key) &&
                            cmp.Equals(pk, key)
                            // otherwise try & find index of the key
                            || dic.TryGetValue(key, out index))
                        {
                            counts[index]++;
                        }
Example #2
0
        /// <summary>
        /// Applies an accumulator function over sequence element keys,
        /// returning the keys along with intermediate accumulator states. An
        /// additional parameter specifies the comparer to use to compare keys.
        /// </summary>
        /// <typeparam name="TSource">Type of the elements of the source sequence.</typeparam>
        /// <typeparam name="TKey">The type of the key.</typeparam>
        /// <typeparam name="TState">Type of the state.</typeparam>
        /// <param name="source">The source sequence.</param>
        /// <param name="keySelector">
        /// A function that returns the key given an element.</param>
        /// <param name="seedSelector">
        /// A function to determine the initial value for the accumulator that is
        /// invoked once per key encountered.</param>
        /// <param name="accumulator">
        /// An accumulator function invoked for each element.</param>
        /// <param name="comparer">The equality comparer to use to determine
        /// whether or not keys are equal. If <c>null</c>, the default equality
        /// comparer for <typeparamref name="TSource"/> is used.</param>
        /// <returns>
        /// A sequence of keys paired with intermediate accumulator states.
        /// </returns>

        public static IEnumerable <KeyValuePair <TKey, TState> > ScanBy <TSource, TKey, TState>(
            this IEnumerable <TSource> source,
            Func <TSource, TKey> keySelector,
            Func <TKey, TState> seedSelector,
            Func <TState, TKey, TSource, TState> accumulator,
            IEqualityComparer <TKey>?comparer)
        {
            if (source == null)
            {
                throw new ArgumentNullException(nameof(source));
            }
            if (keySelector == null)
            {
                throw new ArgumentNullException(nameof(keySelector));
            }
            if (seedSelector == null)
            {
                throw new ArgumentNullException(nameof(seedSelector));
            }
            if (accumulator == null)
            {
                throw new ArgumentNullException(nameof(accumulator));
            }

            return(_(comparer ?? EqualityComparer <TKey> .Default));

            IEnumerable <KeyValuePair <TKey, TState> > _(IEqualityComparer <TKey> comparer)
            {
                var stateByKey = new Collections.Dictionary <TKey, TState>(comparer);

                (bool, TKey, TState)prev = default;

                foreach (var item in source)
                {
                    var key = keySelector(item);

                    var state = // key same as the previous? then re-use the state
                                prev is (true, {} pk, {} ps) &&
                                comparer.GetHashCode(pk) == comparer.GetHashCode(key) &&
                                comparer.Equals(pk, key) ? ps
                              : // otherwise try & find state of the key
                                stateByKey.TryGetValue(key, out var ns) ? ns
                              : seedSelector(key);

                    state = accumulator(state, key, item);

                    stateByKey[key] = state;

                    yield return(new KeyValuePair <TKey, TState>(key, state));

                    prev = (true, key, state);
                }
            }
        }