关于 linq:识别 C# 列表中的唯一值

Identifying Unique Values in a C# List

我创建了一个类,如下所示,来表示复合主键模型:

1
2
3
4
5
6
public class PrimaryKeyModel
{
    public string ColumnName { get; set; }
    public string ColumnValue { get; set; }
    public int RowNumber { get; set; } // always unique
}

它基本上代表了组成主键的列的名称/值,加上这个组合所属的行号;最初在电子表格中。

然后我把这个模型放在一个列表中,并用电子表格中的数据填充它:

1
List<PrimaryKeyModel> primaryKeysList = new List<PrimaryKeyModel>;

我想检查primaryKeysList,看看它是否有任何重复的值,如果有,我想知道这些值重复的行号。

我尝试了不同的方法,例如将此列表加载到 HashSet、字典中,并在此链接上使用此解决方案,但没有一个有效。无论如何我可以解决这个问题。

谢谢。

更新 - 这是一个示例数据显示。 UniqueColumnsModel 与 PrimaryKeyModel 相同;我已经在这里更改了它以使其更清晰。

enter


GroupBy 很适合这个:

1
2
3
primaryKeysList.GroupBy(pk => new {pk.ColumnName, pk.ColumnValue})
               .Where(g => g.Count() > 1)
               .SelectMany(g => g);   // flatten the groups into a single list


如果你的类代表这种结构:

1
2
3
4
5
6
7
ColumnName    ColumnValue   RowNumber
Id            3             1
Id2           1             1
Id            1             2
Id2           2             2
Id            3             3
Id2           1             3 //duplicate

那么到目前为止所有其他答案都不正确,您需要以不同的方式进行操作,按行号分组,然后逐个比较每个字段。因为相等是可交换的,所以我们可以稍微加快循环速度,这样我们就不会对每个项目进行两次比较。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
List<PrimaryKeyModel> keys = new List<PrimaryKeyModel>()
{
        new PrimaryKeyModel("Id","3", 1),
        new PrimaryKeyModel("Id2","1", 1),
        new PrimaryKeyModel("Id","1", 2),
        new PrimaryKeyModel("Id2","1", 2),
        new PrimaryKeyModel("Id","3", 3),
        new PrimaryKeyModel("Id2","1", 3),
};

var groupedKeys = keys.OrderBy(pk => pk.ColumnName).GroupBy(k => k.RowNumber).ToList();
HashSet<int> duplicateRowNumbers = new HashSet<int>();

for (int i = 0; i < groupedKeys.Count - 1; i++)
{
    for (int j = i + 1; j < groupedKeys.Count; j++)
    {
        if (AreTheSame(groupedKeys[i], groupedKeys[j]))
        {
            duplicateRowNumbers.Add(groupedKeys[i].First().RowNumber);
            duplicateRowNumbers.Add(groupedKeys[j].First().RowNumber);
        }
    }
}

private static bool AreTheSame(IEnumerable<PrimaryKeyModel> a, IEnumerable<PrimaryKeyModel> b)
{
    var leftEnumerator = a.GetEnumerator();
    var rightEnumerator = b.GetEnumerator();
    while (leftEnumerator.MoveNext() | rightEnumerator.MoveNext())
    {
        if (leftEnumerator.Current == null) return false;
        if (rightEnumerator.Current == null) return false;
        if (leftEnumerator.Current.ColumnValue != rightEnumerator.Current.ColumnValue) return false;
    }

    return true;
}


编辑:我很可能误读了这个问题,并且从您的类名 PrimaryKeyModel 中推断出太多 - 我将其解释为主键的模型,并且您想找到重复的主键。如果不是这种情况,我敦促您重新考虑您的命名...此时,D Stanley 的答案可能就是您想要的,但您应该将 ColumnName/ColumnValue 视为"主键"" 这里 - 从逻辑上讲,行号不是键的一部分。

原答案

您似乎没有覆盖 Equals(object)GetHashCode - 这意味着每个对象都被认为与其他对象不同。你可能想要这样的东西:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public sealed class PrimaryKeyModel : IEquatable<PrimaryKeyModel>
{
    // TODO: Make these read-only (mutable keys are a bad idea...)
    public string ColumnName { get; set; }
    public string ColumnValue { get; set; }
    public int RowNumber { get; set; }

    public override bool Equals(object other)
    {
        return Equals(other as PrimaryKeyModel);
    }

    public bool Equals(PrimaryKeyModel other)
    {
        return other != null &&
               ColumnName == other.ColumnName &&
               ColumnValue == other.ColumnValue &&
               RowNumber == other.RowNumber;
    }

    public override int GetHashCode()
    {
        int hash = 23;
        hash = hash * 31 + ColumnName == null ? 0 : ColumnName.GetHashCode();
        hash = hash * 31 + ColumnValue == null ? 0 : ColumnValue.GetHashCode();
        hash = hash * 31 + RowNumber;
        return hash;
    }
}

这是假设您真的希望所有三个字段都相同 - 如果您只关心 RowNumber,则可以简化这些实现(但此时它是一个奇怪的主键)。

之后,您可以使用 Distinct()HashSetDictionary 等。当然,另一种方法是按不同的属性显式分组 - 但感觉应该明智地实现相等。正如评论中所述,我会敦促您将属性设置为只读。


这是对我有用的最终解决方案。这确保了列表的一行中不存在重复项,即列表列表。它基本上将列表的内容倒入一个哈希集中,如果列表中已经存在新添加的项目,则返回 false:

感谢所有为解决上述问题做出贡献的人!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
HashSet<string> primaryKeyChecker = new HashSet<string>();

foreach (var row in rows)
{

    StringBuilder primaryKey = new StringBuilder();
    //Get rowCount;

    foreach (var column in columns)
    {
        (if column is a composite of a primaryKey)
        {
            get column value;
            append it to stringBuilder to form the primaryKey
        }  
    }

                            var addOutcome = primaryKeyChecker.Add(primaryKey.ToString());

                            if (!addOutcome)
                            {
                                //Report a duplicate record and give the rowNumber where this occured.
                            }


}

更新

要解决下面@Bas 突出显示的问题,只需确保在连接主键时;用 coma 或 0 分隔它们,以便突出显示的场景不会发生.. 所以做这样的事情:

1
  primaryKey.Append(currentValue +",");