public bool Equals(GroupByState <V> that)
        {
            if (this.elements.Count != that.elements.Count)
            {
                return(false);
            }

            for (int i = 0; i < elements.Count; i++)
            {
                if (!elements.Array[i].Equals(that.elements.Array[i]))
                {
                    return(false);
                }
            }

            return(true);
        }
        protected override GroupByState <V> UpdateState(K key, GroupByState <V> state, int updateRootIndex)
        {
            if (state.elements.Array == null)
            {
                state.elements = new ResizeableSubArray <Weighted <V> >(0);
            }

            updateAccumulator.Clear();
            resultAccumulator.Clear();

            // first determine and subtract the previous outputs
            groupContent.Clear();
            for (int i = 0; i < state.elements.Count; i++)
            {
                // set the weight associated with this record
                updateAccumulator[state.elements.Array[i].record] = state.elements.Array[i].weight;

                // add the element to the materialized list
                groupContent.Add(state.elements.Array[i].record);

                var weight = (state.elements.Array[i].weight - (i == state.elements.Count - 1 ? 0 : state.elements.Array[i + 1].weight)) / 2;
                if (weight != 0)
                {
                    var result = resultSelector(key, groupContent);

                    if (!resultAccumulator.ContainsKey(result))
                    {
                        resultAccumulator.Add(result, 0);
                    }

                    resultAccumulator[result] -= weight;
                }
            }

            // update the accumulation based on the updates we see.
            for (int index = updateRootIndex; index >= 0; index = updateChain[index].previous)
            {
                var selected = selector(updateChain[index].update.record);
                if (!updateAccumulator.ContainsKey(selected))
                {
                    updateAccumulator.Add(selected, 0);
                }

                updateAccumulator[selected] += updateChain[index].update.weight;
            }

            // load the new accumulation into state
            state.elements.Clear();
            foreach (var pair in updateAccumulator.OrderByDescending(x => x.Value))
            {
                if (pair.Value > 0)
                {
                    state.elements.Add(new Weighted <V>(pair.Key, pair.Value));
                }
                if (pair.Value < 0)
                {
                    Console.Error.WriteLine("Negative accumulation in GroupBy; probably a bug somewhere");
                }
            }

            // second determine and add the current outputs
            groupContent.Clear();
            for (int i = 0; i < state.elements.Count; i++)
            {
                // add the element to the materialized list
                groupContent.Add(state.elements.Array[i].record);
                var weight = (state.elements.Array[i].weight - (i == state.elements.Count - 1 ? 0 : state.elements.Array[i + 1].weight)) / 2;
                if (weight != 0)
                {
                    var result = resultSelector(key, groupContent);

                    if (!resultAccumulator.ContainsKey(result))
                    {
                        resultAccumulator.Add(result, 0);
                    }

                    // subtract half the difference to the next weight, with zero for the last weight
                    resultAccumulator[result] += weight;
                }
            }

            foreach (var pair in resultAccumulator)
            {
                if (pair.Value != 0)
                {
                    Send(new Weighted <R>(pair.Key, pair.Value));
                }
            }

            return(state);
        }