【问题标题】:Generic list comparison in C#C#中的通用列表比较
【发布时间】:2015-12-08 16:39:32
【问题描述】:

我有一种方法可以使用字典查找两个整数列表之间的差异。本质上,代码循环第一个列表,将每个 int 添加到字典中并设置(在不存在的情况下为 1)/增加值。然后它循环第二个列表设置(到-1,其中不存在)/递减值。

一旦它循环了两个列表,您最终会得到一个字典,其中值 = 0 的键表示匹配,值 >=1 的键表示仅存在于第一个列表中,值

首先,这是一个合理的实现吗?

其次,我想让它更通用,目前它只能处理基于 int 的列表。我想要一些可以处理调用者可能定义比较逻辑的任何对象...

    public static Dictionary<int, int> CompareLists(List<int> listA, List<int> listB)
    {
        // 0        Match
        // <= -1    listB only
        // >= 1     listA only
        var recTable = new Dictionary<int, int>();

        foreach (int value in listA)
        {
            if (recTable.ContainsKey(value))
                recTable[value]++;
            else
                recTable[value] = 1;
        }

        foreach (int value in listB)
        {
            if (recTable.ContainsKey(value))
                recTable[value]--;
            else
                recTable[value] = -1;
        }

        return recTable;

    }

提前致谢!

回应: “例如,如果您的相同值在 listA 中出现两次,而在 listB 中出现一次,结果将是肯定的,即在您的 cmets 中显示“仅 listA”,它将无法正常工作。”

让我澄清一下;如果一个值在 listA 中出现两次,它也应该在 listB 中出现两次 - 因此,如果一个值在 listA 中出现两次,在 listB 中出现一次,我不在乎它从 listA 中选择匹配哪一个,只要一个不协调项目报告正确。

想象一下您尝试在两个文件之间核对大量付款金额的用例,重复金额是完全可行的,但只要不核对值匹配哪个重复项并不重要被报道。

【问题讨论】:

  • Firstly, is this a sensible implementation? 对于 CodeReview 站点可能会更好,因为它是工作代码。 I'd like something that could handle any object where the caller could potentially define the comparison logic你签出IComparable&lt;T&gt;了吗? (编辑:或者实际上是IEquatable&lt;T&gt;?)
  • 如果你有相同的值在listA 中出现两次,在listB 中出现一次,结果将是肯定的,这将在你的 cmets 中显示“仅 listA”。 /跨度>
  • 您刚刚重新创建了完整的外部联接。
  • 什么的合理实现?如果你解释了你的功能背后的目的,人们可以提出更好的方法来实现它。

标签: c# linq generics


【解决方案1】:

为了回答你的第二个问题,这里是如何使它更通用:

public static Dictionary<T, int> CompareLists<T>(IEnumerable<T> listA, 
    IEnumerable<T> listB, IEqualityComparer<T> comp)
{
    var recTable = new Dictionary<T, int>(comp);

    foreach (var value in listA)
    {
        if (recTable.ContainsKey(value))
            recTable[value]++;
        else
            recTable[value] = 1;
    }

    foreach (var value in listB)
    {
        if (recTable.ContainsKey(value))
            recTable[value]--;
        else
            recTable[value] = -1;
    }

    return recTable;
}

这更通用,因为:

  • 我传入类型 T 而不是 int。
  • 我使用 IEnumerables 而不是 Lists。
  • 我传入一个 IEqualityComparer 并将其传递给需要使用它的 Dictionary 构造函数。
  • 我在 foreach 循环中使用var 而不是int。您也可以使用T

你这样称呼这段代码:

static void Main()
{
    int[] arr1 = { 1, 2, 3 };
    int[] arr2 = { 3, 2, 1 };

    var obj = CompareLists(arr1, arr2, EqualityComparer<int>.Default);

    Console.ReadLine();
}

这是一个实现 IEqualityComparer 的示例。这将所有奇数整数视为相等,将所有偶数整数视为相等:

public class MyEq : IEqualityComparer<int>
{
    public bool Equals(int x, int y)
    {
        return (x % 2) == (y % 2);
    }

    public int GetHashCode(int obj)
    {
        return (obj % 2).GetHashCode();
    }
}

【讨论】:

  • 谢谢你,它非常适合我想做的事情。
【解决方案2】:

FullOuterJoin 在这里找到:LINQ - Full Outer Join

public static Dictionary<int, int> CompareLists(List<int> listA, List<int> listB)
{
  return listA.FullOuterJoin(listB,
    a=>a, // What to compare from ListA
    b=>b, // What to compare from ListB
    (a,b,key)=>
      new {key=key,value=0}, // What to return if found in both
      new {key=key,value=-1},// What to return if found only in A
      new {key=key,value=1}) // What to return if found only in B
    .ToDictionary(a=>a.key,a=>a.value); // Only because you want a dictionary
}

【讨论】:

  • 我很难看出这比 user2023861 发布的解决方案更简洁/更好?快乐再教育,就是没看到?
  • 好吧,这个方法适用于所有类型的集合、列表,它是通用的,因为它允许您确定要比较的内容和保留的内容。当然,在您的情况下,您想要的只是 0、-1 或 1,但是如果您想要整个记录怎么办?或者你想比较一个对象列表的属性(或多个属性)?
  • 它也是一个定义明确且已知的方法(尽管实现相当新),已经存在了 30 年:en.wikipedia.org/wiki/Join_(SQL)#Full_outer_join
  • 使用 FullOuterJoin,列表类型不必是同一类型。它们也可以是完整的对象,您不必编写自定义的 IEqualityComparer,只需列出要比较的对象的哪些属性。它非常适合其他 LINQ 方法,并且可以很容易地转换为 SQL(FULL OUTER JOIN)。您还可以返回部分对象,或合并每个对象的一部分。它几乎是通用的,每个人都应该从名称中了解完全外部连接的作用。
  • 感谢您提供更多详细信息。与完全外部联接的概念相反,我正在努力解决的问题更多的是新的实现。它的好处现在更有意义了。
【解决方案3】:

您可以使用泛型来做到这一点:

public static Dictionary<T, int> CompareLists<T>(List<T> listA, List<T> listB)
{
    // 0        Match
    // <= -1    listB only
    // >= 1     listA only
    var recTable = new Dictionary<T, int>();

    foreach (T value in listA)
    {
        if (recTable.ContainsKey(value))
            recTable[value]++;
        else
            recTable[value] = 1;
    }

    foreach (T value in listB)
    {
        if (recTable.ContainsKey(value))
            recTable[value]--;
        else
            recTable[value] = -1;
    }

    return recTable;

}

【讨论】:

    【解决方案4】:

    这是我的两分钱:

    public static Dictionary<T, int> CompareLists<T>(List<T> left, List<T> right, IEqualityComparer<T> comparer)
    {
        Dictionary<T, int> result = left.ToDictionary(l => l, l => right.Any(r => comparer.Equals(l, r)) ? 0 : -1);
        foreach (T r in right.Where(t => result.Keys.All(k => !comparer.Equals(k, t))))
            result[r] = 1;
        return result;
    }
    

    该方法采用任何类型的Lists T 和该类型的IEqualityComparer T。然后它首先生成包含在“左”List 中的那些元素的字典,从而检查它们是否也在“右”List 中并相应地设置值。

    第二步添加仅包含在“右”List 中的元素,值为1

    这是否是一个明智的实现取决于您试图用它实现什么。我认为这是一个简短但仍然可读的,依赖于 LINQ 方法的正确实现。如果这是针对非常大的列表或经常调用的方法,可能会有更快的可能性。

    【讨论】:

    • 这当然不适用于您在一个或两个列表中多次出现相同键的附加要求。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-01-31
    • 1970-01-01
    • 2023-04-04
    • 1970-01-01
    相关资源
    最近更新 更多