public LeftItemContainer([AllowNull] LeftItemType leftItem, [AllowNull] CommonValueType commonValue, int index, IndexShifter <CollectionChange <LeftItemType, RightItemType> > shifter) : base(commonValue, index) { LeftItem = leftItem; shifter = shifter ?? throw new ArgumentNullException(nameof(shifter)); shifter.IndexShiftConditionEvaluating += Shifter_IndexShiftConditionEvaluating; }
/// <summary> /// Get step by step changes to reorder the original left value collection, without to need to clear it. /// The changes can contain adding, replacing and removing statements, but nothing else. /// </summary> /// <typeparam name="ItemType"></typeparam> /// <param name="leftItems"></param> /// <param name="rigthItems"></param> /// <param name="commonValueEqualityComparer"></param> /// <returns></returns> public static IEnumerable <CollectionChange <LeftItemType, RightItemType> > GetCollectionChanges <LeftItemType, RightItemType, CommonValueType>(this IEnumerable <LeftItemType> leftItems, IEnumerable <RightItemType> rigthItems, Func <LeftItemType, CommonValueType> getCommonValueFromLeftItem, Func <RightItemType, CommonValueType> getCommonValueFromRightItem, IEqualityComparer <CommonValueType> commonValueEqualityComparer) { commonValueEqualityComparer = commonValueEqualityComparer ?? EqualityComparer <CommonValueType> .Default; var commonValueContainerEqualityComparer = new CommonValueContainerEqualityComparer <CommonValueType>(commonValueEqualityComparer); var leftItemsEnumerator = leftItems.GetEnumerator(); var rightItemsEnumerator = rigthItems.GetEnumerator(); var hasLeftItem = true; var hasRightItem = true; // It will only count until a left or right item is not found var earlyIterationCount = 0; var earlyLeftValueIndex = 0; var earlyRightValueIndex = 0; // Early changes that are synchronized or are synchronized but having // removing or adding changes at the end can be returned all at once. // This list represents the cache for it. var earlyIterationChanges = new List <CollectionChange <LeftItemType, RightItemType> >(); var leftItemIndexShifter = new IndexShifter <CollectionChange <LeftItemType, RightItemType> >(); var lateLeftItemContainers = new OrderedHashSet <CommonValueContainer <CommonValueType>, LeftItemContainer <LeftItemType, RightItemType, CommonValueType> >(commonValueContainerEqualityComparer); var lateRightItemContainers = new OrderedHashSet <CommonValueContainer <CommonValueType>, RightItemContainer <LeftItemType, RightItemType, CommonValueType> >(commonValueContainerEqualityComparer); var areEarlyIterationValuesEqual = true; bool hasValue(ref bool hasItem, IEnumerator itemEnumerator) => hasItem && (hasItem = itemEnumerator.MoveNext()); while (hasValue(ref hasLeftItem, leftItemsEnumerator) | hasValue(ref hasRightItem, rightItemsEnumerator)) { // Cancel if not a left and right value is available if (!(hasLeftItem || hasRightItem)) { break; } var leftItem = hasLeftItem ? leftItemsEnumerator.Current : default; var leftCommonValue = hasLeftItem ? getCommonValueFromLeftItem(leftItem !) : default; var rightItem = hasRightItem ? rightItemsEnumerator.Current : default; var rightCommonValue = hasRightItem ? getCommonValueFromRightItem(rightItem !) : default; CollectionChange <LeftItemType, RightItemType>?earlyIterationChange = default; LeftItemContainer <LeftItemType, RightItemType, CommonValueType> createLeftItemContainer() => new LeftItemContainer <LeftItemType, RightItemType, CommonValueType>(leftItem, leftCommonValue, earlyLeftValueIndex, leftItemIndexShifter); RightItemContainer <LeftItemType, RightItemType, CommonValueType> createRightItemContainer() => new RightItemContainer <LeftItemType, RightItemType, CommonValueType>(rightItem, rightCommonValue, earlyRightValueIndex); if (hasLeftItem && hasRightItem) { var areBothItemsEqual = commonValueEqualityComparer.Equals(leftCommonValue !, rightCommonValue !); if (areBothItemsEqual) { earlyIterationChange = new CollectionChange <LeftItemType, RightItemType>(NotifyCollectionChangedAction.Replace, leftItem, earlyLeftValueIndex, rightItem, earlyRightValueIndex); // We only move forward to back, but never back to forward, so this item, even when // it's now well positioned, needs to be checked, if it is still well positioned. var rightItemContainer = createRightItemContainer(); // We take use of a cache, so that we don't have to search for left item and don't replace it twice rightItemContainer.CachedLeftItem = createLeftItemContainer(); lateRightItemContainers.Add(rightItemContainer); } else { lateLeftItemContainers.Add(createLeftItemContainer()); lateRightItemContainers.Add(createRightItemContainer()); areEarlyIterationValuesEqual = false; } earlyIterationCount++; earlyLeftValueIndex = earlyIterationCount; earlyRightValueIndex = earlyIterationCount; } else if (hasLeftItem) { if (areEarlyIterationValuesEqual) { // When early iterations are synchronized, then we always need // to delete the left value at index of the greatest right value index plus one var newLeftValueIndex = earlyLeftValueIndex - (earlyLeftValueIndex - earlyRightValueIndex); earlyIterationChange = CollectionChange <LeftItemType, RightItemType> .CreateOld(NotifyCollectionChangedAction.Remove, leftItem, newLeftValueIndex); } else { lateLeftItemContainers.Add(createLeftItemContainer()); } earlyLeftValueIndex++; } else { if (areEarlyIterationValuesEqual) { earlyIterationChange = CollectionChange <LeftItemType, RightItemType> .CreateNew(NotifyCollectionChangedAction.Add, rightItem, earlyRightValueIndex); } else { lateRightItemContainers.Add(createRightItemContainer()); } earlyRightValueIndex++; } if (earlyIterationChange != null) { earlyIterationChanges.Add(earlyIterationChange); } } foreach (var change in earlyIterationChanges) { yield return(change); } // Exit only if both lists were synchronized from early on if (areEarlyIterationValuesEqual) { yield break; } foreach (var rightItemContainer in lateRightItemContainers) { // This index represents the current index of the right value collection. var rightValueIndex = rightItemContainer.ShiftedIndex; var rightValue = rightItemContainer.CommonValue; var hasCachedLeftItem = rightItemContainer.CachedLeftItem != null; LeftItemContainer <LeftItemType, RightItemType, CommonValueType>?foundLeftValueIndexPair = null; if (hasCachedLeftItem) { foundLeftValueIndexPair = rightItemContainer.CachedLeftItem; } else { if (lateLeftItemContainers.TryGetValue(CommonValueContainer <CommonValueType> .CreateEqualComparableItem(rightValue), out foundLeftValueIndexPair !)) { lateLeftItemContainers.Remove(foundLeftValueIndexPair); } } var rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount = rightValueIndex; if (foundLeftValueIndexPair == null) { var change = CollectionChange <LeftItemType, RightItemType> .CreateNew(NotifyCollectionChangedAction.Add, rightItemContainer.RightItem, rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount); yield return(change); leftItemIndexShifter.Shift(change); } else { var foundLeftIndex = foundLeftValueIndexPair.ShiftedIndex; // Indexes can be equal, when not processed items are before the moved item if (foundLeftIndex != rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount) { // Here we deny the forward to backward move, so we have to process already replaced items, // and move them forward, because they could be now behind those skipped items, but need to // be moved before them if (foundLeftIndex < rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount) { continue; } var change = new CollectionChange <LeftItemType, RightItemType>(NotifyCollectionChangedAction.Move, foundLeftValueIndexPair.LeftItem, foundLeftIndex, rightItemContainer.RightItem, rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount); // We move the old existing item yield return(change); leftItemIndexShifter.Shift(change); } if (!hasCachedLeftItem) { // Then we replace the left item by moved item at the destination index of the moved item yield return(new CollectionChange <LeftItemType, RightItemType>(NotifyCollectionChangedAction.Replace, foundLeftValueIndexPair.LeftItem, rightValueIndexWithNotProcessedItemsBeforeRightValueIndexCount, rightItemContainer.RightItem, rightValueIndex)); } } } // We remove all left left-value-index-pairs, because they did not match any condition above and have to be removed in REVERSED order foreach (var leftValueIndexPair in lateLeftItemContainers.YieldReversedItems()) { yield return(CollectionChange <LeftItemType, RightItemType> .CreateOld(NotifyCollectionChangedAction.Remove, leftValueIndexPair.LeftItem, leftValueIndexPair.ShiftedIndex)); } }