在C#2.0中同步两个IList的最佳算法

时间:2020-03-06 15:00:58  来源:igfitidea点击:

想象一下以下类型:

public struct Account
{
    public int Id;
    public double Amount;
}

在C2.0中同步两个IList <Account>的最佳算法是什么? (没有linq)?

第一个列表(L1)是参考列表,第二个列表(L2)是根据第一个列表进行同步的列表:

  • L2中不再存在的L2中的所有帐户必须从L2中删除
  • 必须更新L2中仍然存在的L2中的所有帐户(金额属性)
  • L1中但尚未在L2中的所有帐户都必须添加到L2中

该ID标识帐户。找到一个幼稚且有效的算法并不难,但是我想知道是否有一个聪明的解决方案来处理这种情况而不会破坏可读性和性能。

编辑 :

  • 帐户类型无关紧要,可以是一个类,具有属性,相等成员等。
  • L1和L2未排序
  • L2项不能被L1项替换,必须对其进行更新(逐字段,逐属性)

解决方案

首先,我将摆脱可变的结构。可变值类型从根本上来说是一件坏事。 (与公共领域一样,IMO。)

建立字典可能是值得的,因此我们可以轻松比较两个列表的内容。一旦有了检查存在/不存在的简便方法,其余的都应该很简单。

老实说,听起来我们基本上希望L2是L1的完整副本...清除L2并只是调用AddRange?还是在更改L2时还想采取其他措施的观点?

除了Jon Skeet的注释之外,还使Account构造一个类并重写Equals()和GetHashCode()方法以获得不错的相等性检查。

L2 = L1.clone()?

...但是我猜你忘了提些什么。

如果两个列表已排序,那么我们可以简单地串联浏览它们。这是O(m + n)运算。以下代码可以:

class Program
{
    static void Main()
    {
        List<string> left = new List<string> { "Alice", "Charles", "Derek" };
        List<string> right = new List<string> { "Bob", "Charles", "Ernie" };

        EnumerableExtensions.CompareSortedCollections(left, right, StringComparer.CurrentCultureIgnoreCase,
            s => Console.WriteLine("Left: " + s), s => Console.WriteLine("Right: " + s), (x,y) => Console.WriteLine("Both: " + x + y));
    }
}

static class EnumerableExtensions
{
    public static void CompareSortedCollections<T>(IEnumerable<T> source, IEnumerable<T> destination, IComparer<T> comparer, Action<T> onLeftOnly, Action<T> onRightOnly, Action<T, T> onBoth)
    {
        EnumerableIterator<T> sourceIterator = new EnumerableIterator<T>(source);
        EnumerableIterator<T> destinationIterator = new EnumerableIterator<T>(destination);

        while (sourceIterator.HasCurrent && destinationIterator.HasCurrent)
        {
            // While LHS < RHS, the items in LHS aren't in RHS
            while (sourceIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) < 0))
            {
                onLeftOnly(sourceIterator.Current);
                sourceIterator.MoveNext();
            }

            // While RHS < LHS, the items in RHS aren't in LHS
            while (sourceIterator.HasCurrent && destinationIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) > 0))
            {
                onRightOnly(destinationIterator.Current);
                destinationIterator.MoveNext();
            }

            // While LHS==RHS, the items are in both
            while (sourceIterator.HasCurrent && destinationIterator.HasCurrent && (comparer.Compare(sourceIterator.Current, destinationIterator.Current) == 0))
            {
                onBoth(sourceIterator.Current, destinationIterator.Current);
                sourceIterator.MoveNext();
                destinationIterator.MoveNext();
            }
        }

        // Mop up.
        while (sourceIterator.HasCurrent)
        {
            onLeftOnly(sourceIterator.Current);
            sourceIterator.MoveNext();
        }

        while (destinationIterator.HasCurrent)
        {
            onRightOnly(destinationIterator.Current);
            destinationIterator.MoveNext();
        }
    }
}

internal class EnumerableIterator<T>
{
    private readonly IEnumerator<T> _enumerator;

    public EnumerableIterator(IEnumerable<T> enumerable)
    {
        _enumerator = enumerable.GetEnumerator();
        MoveNext();
    }

    public bool HasCurrent { get; private set; }

    public T Current
    {
        get { return _enumerator.Current; }
    }

    public void MoveNext()
    {
        HasCurrent = _enumerator.MoveNext();
    }
}

不过,在遍历集合时,我们必须小心修改它们。

如果未对它们进行排序,则将一个元素中的每个元素与另一个元素中的每个元素进行比较的结果就是O(mn),这会很快使我们感到痛苦。

如果我们愿意将每个集合中的键值复制到Dictionary或者类似字典中(即,当被问到"是否存在X?"时性能可接受的集合),那么我们可以提出一些合理的建议。

我知道这是一篇旧文章,但是我们应该查看AutoMapper。它会以非常灵活和可配置的方式完全满足要求。

自动贴图