Вступление
Я реализовал два алгоритма: один для отсортированных и один для последовательных коллекций. Оба поддерживают нулевые значения и дубликаты и работают одинаково:
Они возвращают CollectionModification<LeftItemType,RightItemType>
s, что похоже на CollectionChangedEventArgs<T>
(ссылка), которую можно использовать взамен для синхронизации коллекции.
Относительно доходности:
При использовании того или иного алгоритма, в котором ваши левые элементы (ссылочная коллекция) сравниваются с правыми элементами, вы можете применить каждый возвращаемый доход CollectionModification
, как только они возвращаются, но это может привести к тому, что коллекция была изменена-исключением (для пример при использовании List<T>.GetEnumerator
). Чтобы предотвратить это, в обоих алгоритмах реализована возможность использовать индексируемую коллекцию в качестве эталонной коллекции, которая должна быть изменена. Вам нужно только обернуть эталонную коллекцию с помощью YieldIteratorInfluencedReadOnlyList<ItemType>
(abstract), используя методы расширения в YieldIteratorInfluencedReadOnlyListExtensions. :)
SortedCollectionModifications
Первый алгоритм работает для восходящих или нисходящих упорядоченных списков и использует IComparer<T>
.
/// <summary>
/// The algorithm creates modifications that can transform one collection into another collection.
/// The collection modifications may be used to transform <paramref name="leftItems"/>.
/// Assumes <paramref name="leftItems"/> and <paramref name="rightItems"/> to be sorted by that order you specify by <paramref name="collectionOrder"/>.
/// Duplications are allowed but take into account that duplications are yielded as they are appearing.
/// </summary>
/// <typeparam name="LeftItemType">The type of left items.</typeparam>
/// <typeparam name="RightItemType">The type of right items.</typeparam>
/// <typeparam name="ComparablePartType">The type of the comparable part of left item and right item.</typeparam>
/// <param name="leftItems">The collection you want to have transformed.</param>
/// <param name="getComparablePartOfLeftItem">The part of left item that is comparable with part of right item.</param>
/// <param name="rightItems">The collection in which <paramref name="leftItems"/> could be transformed.</param>
/// <param name="getComparablePartOfRightItem">The part of right item that is comparable with part of left item.</param>
/// <param name="collectionOrder">the presumed order of items to be used to determine <see cref="IComparer{T}.Compare(T, T)"/> argument assignment.</param>
/// <param name="comparer">The comparer to be used to compare comparable parts of left and right item.</param>
/// <param name="yieldCapabilities">The yieldCapabilities that regulates how <paramref name="leftItems"/> and <paramref name="rightItems"/> are synchronized.</param>
/// <returns>The collection modifications.</returns>
/// <exception cref="ArgumentNullException">Thrown when non-nullable arguments are null.</exception>
public static IEnumerable<CollectionModification<LeftItemType, RightItemType>> YieldCollectionModifications<LeftItemType, RightItemType, ComparablePartType>(
IEnumerable<LeftItemType> leftItems,
Func<LeftItemType, ComparablePartType> getComparablePartOfLeftItem,
IEnumerable<RightItemType> rightItems,
Func<RightItemType, ComparablePartType> getComparablePartOfRightItem,
SortedCollectionOrder collectionOrder,
IComparer<ComparablePartType> comparer,
CollectionModificationsYieldCapabilities yieldCapabilities)
Вдохновение для алгоритма Python взято из: Эффективная синхронизация двух экземпляров упорядоченного списка.
РавенствоTrailingCollectionModifications
Второй алгоритм работает для любого порядка и использует IEqualityComparer<T>
.
/// <summary>
/// The algorithm creates modifications that can transform one collection into another collection.
/// The collection modifications may be used to transform <paramref name="leftItems"/>.
/// The more the collection is synchronized in an orderly way, the more efficient the algorithm is.
/// Duplications are allowed but take into account that duplications are yielded as they are appearing.
/// </summary>
/// <typeparam name="LeftItemType">The type of left items.</typeparam>
/// <typeparam name="RightItemType">The type of right items.</typeparam>
/// <typeparam name="ComparablePartType">The type of the comparable part of left item and right item.</typeparam>
/// <param name="leftItems">The collection you want to have transformed.</param>
/// <param name="getComparablePartOfLeftItem">The part of left item that is comparable with part of right item.</param>
/// <param name="rightItems">The collection in which <paramref name="leftItems"/> could be transformed.</param>
/// <param name="getComparablePartOfRightItem">The part of right item that is comparable with part of left item.</param>
/// <param name="equalityComparer">The equality comparer to be used to compare comparable parts.</param>
/// <param name="yieldCapabilities">The yield capabilities, e.g. only insert or only remove.</param>
/// <returns>The collection modifications.</returns>
/// <exception cref="ArgumentNullException">Thrown when non-nullable arguments are null.</exception>
public static IEnumerable<CollectionModification<LeftItemType, RightItemType>> YieldCollectionModifications<LeftItemType, RightItemType, ComparablePartType>(
IEnumerable<LeftItemType> leftItems,
Func<LeftItemType, ComparablePartType> getComparablePartOfLeftItem,
IEnumerable<RightItemType> rightItems,
Func<RightItemType, ComparablePartType> getComparablePartOfRightItem,
IEqualityComparer<ComparablePartType>? equalityComparer,
CollectionModificationsYieldCapabilities yieldCapabilities)
where ComparablePartType : notnull
Требования
Требуется одна из следующих платформ
- .NET-Стандарт 2.0
- .NET Core 3.1
- .NET 5.0
Оба алгоритма созданы с пользовательскими реализованными типами (IndexDirectory
, NullableKeyDictionary
, LinkedBucketList
, чтобы назвать несколько), поэтому я не могу просто скопировать и вставить сюда код, поэтому я хотел бы сослаться на мои следующие пакеты:
Реализация
Ожидаемые занятия
Аккаунт:
public class Account
{
public Account(int id) =>
Id = id;
public int Id { get; }
public double Amount { get; }
}
И следующий класс сравнения равенства элементов коллекции:
Comparer AccountEquality:
public class AccountEqualityComparer : EqualityComparer<Account>
{
public new static AccountEqualityComparer Default = new AccountEqualityComparer();
public override bool Equals([AllowNull] Account x, [AllowNull] Account y) =>
ReferenceEquals(x, y) || (!(x is null && y is null) && x.Id.Equals(y.Id));
public override int GetHashCode([DisallowNull] Account obj) =>
obj.Id;
}
Мои занятия
AccountCollectionViewModel:
using Teronis.Collections.Algorithms.Modifications;
using Teronis.Collections.Synchronization;
using Teronis.Collections.Synchronization.Extensions;
using Teronis.Reflection;
public class AccountCollectionViewModel : SyncingCollectionViewModel<Account, Account>
{
public AccountCollectionViewModel()
: base(CollectionSynchronizationMethod.Sequential(AccountEqualityComparer.Default))
{
// In case of SyncingCollectionViewModel, we have to pass a synchronization method.
//
// Sequential means any order
//
}
protected override Account CreateSubItem(Account superItem) =>
superItem;
protected override void ApplyCollectionItemReplace(in ApplyingCollectionModificationBundle modificationBundle)
{
foreach (var (oldItem, newItem) in modificationBundle.OldSuperItemsNewSuperItemsModification.YieldTuplesForOldItemNewItemReplace())
{
// Implementation detail: update left public property values by right public property values.
TeronisReflectionUtils.UpdateEntityVariables(oldItem, newItem);
}
}
}
Программа:
using System.Diagnostics;
using System.Linq;
class Program
{
static void Main()
{
// Arrange
var collection = new AccountCollectionViewModel();
var initialData = new Account[] {
new Account(5) { Amount = 0 },
new Account(7) { Amount = 0 },
new Account(3) { Amount = 0 }
};
var newData = new Account[] {
new Account(5) { Amount = 10 },
/* Account by ID 7 got removed .. */
/* but account by ID 8 is new. */
new Account(8) { Amount = 10 },
new Account(3) { Amount = 10 }
};
// Act
collection.SynchronizeCollection(initialData);
// Assert
Debug.Assert(collection.SubItems.ElementAt(1).Id == 7, "The account at index 1 has not the ID 7.");
Debug.Assert(collection.SubItems.All(x => x.Amount == 0), "Not all accounts have an amount of 0.");
// Act
collection.SynchronizeCollection(newData);
// Assert
Debug.Assert(collection.SubItems.ElementAt(1).Id == 8, "The account at index 1 has not the ID 8.");
Debug.Assert(collection.SubItems.All(x => x.Amount == 10), "Not all accounts have an amount of 10.");
;
}
}
Вы можете видеть, что я использую SyncingCollectionViewModel, очень тяжелый тип. Это потому, что я не закончил легкий SynchronizableCollection еще не реализован (отсутствуют виртуальные методы для добавления, удаления, замены и т.д.).
person
Teroneko
schedule
23.01.2021