【问题标题】:Filtering Duplicate items in an array based on child array in c#在c#中根据子数组过滤数组中的重复项
【发布时间】:2012-07-10 13:17:06
【问题描述】:

我有一个包含基本代码和位置数组的人员列表。我需要消除列表中具有相同位置的不同基码的人员,并保留具有不同位置的人员。

我尝试使用 IEqualityComparer,并在 linq 中分组,但我没有成功。 你们能告诉我怎么做吗? 这是我的班级结构

public class Person
{
    public string Name { get; set; }

    public List<Location> Locations { get; set; }
}
public class Location
{
    public string Name { get; set; }
    public string BaseCode { get; set; }
}

数据示例

Person 1
Name : John

Locations :
      [0]  Name : India , BaseCode : "AA12"
      [1] Name : USA ,BaseCode : "AA14"
Person 2
Name : John

Locations :
      [0]  Name : India, BaseCode : "AA13"
      [1] Name : USA ,BaseCode : "AA14"
Person 3
Name : John

Locations :
      [0]  Name : India, BaseCode : "AA16"
      [1] Name : UK , BaseCode : "AA17"

我想从我的列表中过滤掉第 2 个人,保留第 1 个人和第 3 个人。请指教

【问题讨论】:

    标签: c# linq filter iequalitycomparer


    【解决方案1】:

    免责声明:此解决方案不专门处理具有不同/相同位置的相同BaseCode;你没有在你的要求中提到这个。


    IEqualityComparer&lt;T&gt;路线

    这里重要的部分是PersonLocationIEqualityComparer&lt;T&gt; 实现:

    class Program
    {
        static void Main(string[] args)
        {
            var p1 = new Person {Name ="John", BaseCode="AA12", Locations = new List<Location>
            {
                new Location { Name = "India" },
                new Location { Name = "USA" }
            }};
    
            var p2 = new Person {Name ="John", BaseCode="AA13", Locations = new List<Location>
            {
                new Location { Name = "India" },
                new Location { Name = "USA" }
            }};
    
            var p3 = new Person {Name ="John", BaseCode="AA14", Locations = new List<Location>
            {
                new Location { Name = "India" },
                new Location { Name = "UK" }
            }};
    
            var persons = new List<Person> { p1, p2, p3 };
    
            // Will not return p2.
            var distinctPersons = persons.Distinct(new PersonComparer()).ToList();
    
            Console.ReadLine();
        }
    }
    
    public class PersonComparer : IEqualityComparer<Person>
    {
        public bool Equals(Person x, Person y)
        {
            if (x == null || y == null)
                return false;
    
            bool samePerson = x.Name == y.Name;
    
            bool sameLocations = !x.Locations
                .Except(y.Locations, new LocationComparer())
                .Any();
    
            return samePerson && sameLocations;
        }
    
        public int GetHashCode(Person obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    
    public class LocationComparer : IEqualityComparer<Location>
    {
        public bool Equals(Location x, Location y)
        {
            if (x == null || y == null)
                return false;
    
            return x.Name == y.Name;
        }
    
        public int GetHashCode(Location obj)
        {
            return obj.Name.GetHashCode();
        }
    }
    

    PersonComparer 使用提供 LocationComparer 的 linq Except 扩展来生成两个位置列表之间的差异列表。

    PersonComparer 然后输入 linq Distinct 方法。


    IEquatable&lt;T&gt;路线

    如果您需要与 BaseCode 一起工作,不同算作“匹配”,我认为这条路线行不通,因为 GetHashCode 没有给你一个区分价值的机会。

    另一种解决方案是在类本身上实现 IEquatable&lt;T&gt; 并覆盖 GetHashCodeDistinctExcept 将遵循此实现:

    public class Person : IEquatable<Person>
    {
        public string Name { get; set; }
        public string BaseCode { get; set; }
        public List<Location> Locations { get; set; }
    
        public bool Equals(Person other)
        {
            if (other == null)
                return false;
    
            bool samePerson = Name == other.Name;
    
            // This is simpler because of IEquatable<Location>
            bool sameLocations = !Locations.Except(other.Locations).Any();
    
            return samePerson && sameLocations;
        }
    
        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }
    }
    
    public class Location : IEquatable<Location>
    {
        public string Name { get; set; }
    
        public bool Equals(Location other)
        {
            if (other == null)
                return false;
    
            return Name == other.Name;
        }
    
        public override int GetHashCode()
        {
            return Name.GetHashCode();
        }
    }
    

    这导致更简单的调用:

    var distinctPersons = persons.Distinct().ToList();
    

    【讨论】:

    • 感谢您的回复,我编辑了我的问题,我在结构中犯了一个大错误,您能再检查一次吗?
    • 您需要更改的一件事是BaseCode 似乎是Location 的成员,而不是Person。这个问题似乎暗示在比较Locations时应该忽略基本代码,所以LocationComparer仍然是正确的。
    • @jmh_gr 当我回答时,BaseCode 是在人身上。但是我的实现对BaseCode 没有任何作用,因为要求没有指定如果BaseCode 相等时该怎么做。
    • @user783662 除了我的示例对象代码之外,解决方案仍然是BaseCode 没有直接使用。当BaseCode 相等时,您没有指定如何处理。
    【解决方案2】:

    我很想写如下内容。我没有检查过y.Locations.Equals() 是否有效,但应该很容易将其替换为具有相同功能的东西。

        List<Person> personList = new List<Person>();
        List<Person> deduplicatedPersonList = new List<Person>();
        personList.ForEach(x =>
        {
            Person existingPerson = personList.Find(y =>
            {
                if (y.Locations.Equals(x.Locations))
                    return false;
                return true;
            });
            if (existingPerson == null)
                deduplicatedPersonList.Add(x);
        });
    

    【讨论】:

    • 感谢您的回复,我编辑了我的问题,我在结构中犯了一个大错误,您能再检查一次吗?在这种情况下,locations.equal 将不起作用。
    【解决方案3】:

    您可以使用 IEquatable 接口,并像这样覆盖 Equal 和 GetHashCode 方法:

    问题更改后编辑:

    public class Location : IEquatable<Location>
    {    
           public string Name { get; set; }     
           public string BaseCode { get; set; 
    
            public bool Equals(Location other)
            {
                if (Object.ReferenceEquals(other, null)) return false;
    
                if (Object.ReferenceEquals(this, other)) return true;
                return BaseCode.Equals(other.BaseCode);
            }
    
            public override int GetHashCode()
            {
                return BaseCode.GetHashCode();
            }
    
    
    } 
    

    所以,现在你可以在 Person 列表中使用 Distinct,它只会返回 distinct name 和 BaseCode。

     var distinctListPerson = PersonList.Distinct().ToList();
    

    您可以从MSDN阅读有关此接口的信息

    【讨论】:

    • 感谢您的回复,我编辑了我的问题,我在结构中犯了一个大错误,您能再检查一次吗?
    【解决方案4】:

    Adam 的解决方案是更“正确”的处理方式。但是如果你想用 LINQ 来做,那么像这样的东西也应该做(请注意,代码需要对位置进行排序并将字符串作为标识符):

    persons
        .GroupBy(x => x.Name)
        .SelectMany(x => x)
            .GroupBy(y => string.Concat(y.Locations.Select(z => z.Name)))
        .SelectMany(x => x
            .GroupBy(y => string.Concat(y.Locations.Select(z => z.BaseCode)))
        .Select(x => x.First());
    

    【讨论】:

    • 感谢您的回复,我正在尝试这个,请您解释一下,并且缺少右括号
    猜你喜欢
    • 2021-10-25
    • 2021-09-02
    • 2015-12-02
    • 2018-05-08
    • 1970-01-01
    • 1970-01-01
    • 2021-10-04
    • 1970-01-01
    • 2020-01-23
    相关资源
    最近更新 更多