使用LINQ从类型集合中过滤重复项

3

我正在通过两个参数进行分组,然后基于创建日期(使用first())选择子组列表中最新的类型来过滤列表。 这将消除x.application和x.externalid属性上的重复。

var list = ((List<SomeType>)xDic)
            .GroupBy(x => new {x.Application, x.ExternalID})
            .OrderByDescending(z => z.First().CreateDate)
            .Select(y => y.First()).ToList();

我遇到的问题是需要定义另一个属性组合(x.application和x.externaldisplayid)进行过滤并按第一个分组。
总结一下,我需要通过过滤掉基于((x.application/x.externalid) OR (x.application/x.externaldisplayid))组合的任何重复项来获取SomeTypes的唯一列表。
Example set:
{ "extID": 1234, "extDspID" : 111, "App" : "Test", "CreateDate": 2/01/2015}
{ "extID": 1234, "extDspID" : 5, "App" : "Test", "CreateDate": 1/01/2015}
{ "extID": 012, "extDspID" : 90, "App" : "Mono", "CreateDate": 6/06/2015}
{ "extID": 999, "extDspID" : 78, "App" : "Epic", "CreateDate": 8/08/2015}
{ "extID": 333, "extDspID" : 78, "App" : "Epic", "CreateDate": 8/12/2015}
{ "extID": 345, "extDspID" : 33, "App" : "Test", "CreateDate": 2/01/2015}
{ "extID": 666, "extDspID" : 33, "App" : "Test", "CreateDate": 1/01/2015}

desired result:
{ "extID": 1234, "extDspID" : 111, "App" : "Test", "CreateDate": 2/01/2015}
{ "extID": 012, "extDspID" : 90, "App" : "Mono", "CreateDate": 6/06/2015}
{ "extID": 333, "extDspID" : 78, "App" : "Epic", "CreateDate": 8/12/2015}
{ "extID": 345, "extDspID" : 33, "App" : "Test", "CreateDate": 2/01/2015}

1
@B.ClayShannon - 我必须考虑最近创建的日期维度,因此使用distinct无法完全解决我的问题。 - melmack
2
你能解释一下 ((x.application/x.externalid) OR (x.application/x.externaldisplayid)) 组合的确切含义吗? - Yacoub Massad
关于排序呢? - Yacoub Massad
@YacoubMassad 如果出现重复,我需要保留创建日期最近的对象。 - melmack
1
我怀疑你无法解释你想要什么才是问题的根源 :) 这三个是否都是重复的呢?{A1,id1,disp1},{A1,id1,disp2},{A1,id2,disp2}? - Alexei Levenkov
显示剩余15条评论
2个回答

3

首先,声明两个等式比较器来指定你的两个条件,如下所示:

public class MyEqualityComparer1 : IEqualityComparer<SomeType>
{
    public bool Equals(SomeType x, SomeType y)
    {
        return x.Application == y.Application && x.ExternalID == y.ExternalID;
    }

    public int GetHashCode(SomeType obj)
    {
        return (obj.Application + obj.ExternalID).GetHashCode();
    }
}

public class MyEqualityComparer2 : IEqualityComparer<SomeType>
{
    public bool Equals(SomeType x, SomeType y)
    {
        return x.Application == y.Application && x.ExternalDisplayId == y.ExternalDisplayId;
    }

    public int GetHashCode(SomeType obj)
    {
        return (obj.Application + obj.ExternalDisplayId).GetHashCode();
    }
}

然后,按照CreatedDate对列表进行排序,然后使用Distinct来过滤您的列表,如下所示:
var result = xDic
    .OrderByDescending(x => x.CreateDate)
    .Distinct(new MyEqualityComparer1())
    .Distinct(new MyEqualityComparer2());
Distinct方法应该删除后面的项目,因此我们应该能够依赖使用OrderByDescending确保Distinct将删除具有较早CreatedTime的项。
然而,由于Distinct的文档没有保证这一点,您可以使用如下的自定义去重方法:
public static class Extensions
{
    public static IEnumerable<T> OrderedDistinct<T>(this IEnumerable<T> enumerable, IEqualityComparer<T> comparer)
    {
        HashSet<T> hash_set = new HashSet<T>(comparer);

        foreach(var item in enumerable)
            if (hash_set.Add(item))
                yield return item;
    }
}

并像这样使用:

var result = xDic
    .OrderByDescending(x => x.CreateDate)
    .OrderedDistinct(new MyEqualityComparer1())
    .OrderedDistinct(new MyEqualityComparer2());

感谢您的回复,@Yacoub。我一有机会就会尝试它。 - melmack
有趣的是,Distinct(x => {x.p1}).Distinct(x=>{x.p2})Distinct(x => {x.p2}).Distinct(x=>{x.p1}) 是否会得到相同的结果(这对于 OP 寻找的内容应该是正确的)。 - Alexei Levenkov
1
@AlexeiLevenkov,是的。所有重复项都已删除,并通过调用两个Distinct操作来保证(不管调用顺序如何)。唯一需要做的就是确保我们始终删除最近的项目。由于我们对列表进行排序,因此我们可以确定任何被任何一个Distinct操作移除的项目在序列中具有较新的CreatedDate的类似项。 - Yacoub Massad

0

目前被接受的答案将不能正确地对“SomeType”对象进行排序,因此也无法产生所需的结果集。

我在这里实现了一个解决方案:

https://dotnetfiddle.net/qBkIXo

我也基于 Distinct(请参见 MSDN 文档 此处)构建了我的解决方案。我生成哈希的方式是基于这个巧妙的方法,它使用了一个匿名类型,例如:

public int GetHashCode(SomeType sometype)
{
 //Calculate the hash code for the SomeType.
 return new { sometype.Application, sometype.ExternalID }.GetHashCode();
}

为了达到正确的期望结果,需要应用分组、排序和使用去重等组合,例如:
    var noduplicates = products.GroupBy(x => new {x.Application, x.ExternalDisplayId})
        .Select(y => y.OrderByDescending(x => x.CreateDate).First())
        .ToList()
        .Distinct(new ApplicationExternalDisplayIdComparer())
        .GroupBy(x => new {x.Application, x.ExternalID})
        .Select(y => y.OrderByDescending(x => x.CreateDate).First())
        .ToList()
        .Distinct(new ApplicationExternalIDComparer());

正如您在fiddle输出中所看到的那样,这将给出您期望的结果。


你为什么认为我的答案没有正确地对对象进行排序?能否请您解释一下? - Yacoub Massad
我认为楼主希望结果按照他在问题中指定的顺序呈现。要实现这种排序,您需要进行分组、按日期排序,然后应用去重。我也有您在fiddle中给出的结果集,但我认为楼主想要指定的排序方式;我的答案按照那个顺序呈现结果。 - Ben Smith
关于排序,我认为 OP 的要求只是确保我们不删除具有最近“CreatedDate”的项目。 - Yacoub Massad
@YacoubMassad是正确的。我没有要求过滤后的集合有序。 - melmack
@melmack 你说你没有需求,那么为什么在你的问题中进行分组和排序?你所期望的结果具有明确的顺序,只有通过像我的答案中那样对分组和排序进行操作才能实现,即所需的结果集不仅按日期降序排序。 - Ben Smith
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接