【问题标题】:Select distinct from List<t> ignoring one element选择不同于 List<t> 忽略一个元素
【发布时间】:2014-02-10 12:09:44
【问题描述】:

我有一个自定义数据类型的列表,简化示例 (myMovies):

public class Movie
{
    public Int32 TVIndex;
    public string MovieName;
    public string MovieRating;
    public string MovieRuntime;
    public List<Actor> MovieActors;
    public List<MovieArt> MovieImages;
}



public class Actor
{
    public string ActorName;
    public string ActorRole;
}

public class MovieArt
{
    public string ImagePath;
}


List<Movie> myMovies = new List<Movie>(); 

现在我正在尝试从myMovies 中删除所有重复项,但忽略TVIndex

我试过看

List<Movie> myDistinctMovies = myMovies.Distinct().ToList(); 

但无法弄清楚如何忽略TvIndex。这可能吗?

【问题讨论】:

  • 您可以覆盖 Movie 数据类型的 Equals 和 GetHashCode。
  • @Grundy/peer..Example 会很棒..
  • 也可以看MoreLINQDistinctBy方法
  • 正在处理它......唯一的麻烦是在真实代码中Movie 有 50 个字段,Actor 有 15 个字段,MovieArt 有 5 个字段。这需要一段时间!!跨度>

标签: c# linq list


【解决方案1】:

基于@Grundy 的回答,包括实施:

public class MovieEqualityComparer : IEqualityComparer<Movie>
{

    public bool Equals(Movie x, Movie y)
    {
        if ( x == null )
            return y == null;

        if ( y == null )
            return x == null;

        if ( object.ReferenceEquals(x, y) )
            return true;

        if ( !string.Equals(x.MovieName, y.MovieName) )
            return false;

        if ( !string.Equals(x.MovieRating, y.MovieRating) )
            return false;

        if ( !string.Equals(x.MovieRuntime, y.MovieRuntime) )
            return false;

        if ( !Enumerable.SequenceEqual(x.MovieActors, y.MovieActors) )
            return false;

        if ( !Enumerable.SequenceEqual(x.MovieImages, y.MovieImages) )
            return false;

        return true;
    }

    public int GetHashCode(Movie obj)
    {
        if ( obj == null )
            throw new ArgumentNullException();

        unchecked
        {
            int hash = 17;
            hash     = hash * 31 + ((obj.MovieName    == null) ? 0 : obj.MovieName.GetHashCode());
            hash     = hash * 31 + ((obj.MovieRating  == null) ? 0 : obj.MovieRating.GetHashCode());
            hash     = hash * 31 + ((obj.MovieRuntime == null) ? 0 : obj.MovieRuntime.GetHashCode());

            if ( obj.MovieActors != null )
            {
                foreach ( var actor in obj.MovieActors )
                    hash = hash * 31 + ((actor == null) ? 0 : actor.GetHashCode());
            }

            if ( obj.MovieImages != null )
            {
                foreach ( var image in obj.MovieImages )
                    hash = hash * 31 + ((image == null) ? 0 : image.GetHashCode());
            }

            return hash;
        }
    }
}

用法同理:

List<Movie> myMovies = new List<Movie>
    {
        // ...
    }; 

List<Movie> myDistinctMovies = myMovies.Distinct(new MovieEqualityComparer()).ToList(); 

编辑

正如@Tim 所指出的,如果您想通过引用相等以外的任何内容进行比较,则必须对其他自定义类型执行非常相似的操作。

public class Actor : IEquatable<Actor>
{
    public string ActorName;
    public string ActorRole;

    public override bool Equals(object obj)
    {
        return this.Equals(obj as Actor);
    }

    public bool Equals(Actor other)
    {
        if ( other == null )
            return false;

        if ( object.ReferenceEquals(this, other) )
            return true;

        if ( !string.Equals(this.ActorName, other.ActorName) )
            return false;

        if ( !string.Equals(this.ActorRole, other.ActorRole) )
            return false;

        return true;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash     = hash * 31 + ((ActorName == null) ? 0 : ActorName.GetHashCode());
            hash     = hash * 31 + ((ActorRole == null) ? 0 : ActorRole.GetHashCode());

            return hash;
        }
    }
}

public class MovieArt : IEquatable<MovieArt>
{
    public string ImagePath;

    public override bool Equals(object obj)
    {
        return this.Equals(obj as MovieArt);
    }

    public bool Equals(MovieArt other)
    {
        if ( other == null )
            return false;

        if ( object.ReferenceEquals(this, other) )
            return true;

        if ( !string.Equals(this.ImagePath, other.ImagePath) )
            return false;

        return true;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            int hash = 17;
            hash     = hash * 31 + ((ImagePath == null) ? 0 : ImagePath.GetHashCode());

            return hash;
        }
    }
}

【讨论】:

  • GetHashCode 中可能需要添加检查null 属性值
  • @Grundy 我改变了算法:)
  • 是的!这就是我的意思。但是1731 的神奇数字是什么?
  • 这是我从互联网上撤下的一个技巧,可以减少哈希码冲突的机会。我相信你可以取任意一组素数而不是 17 和 31。算法保持不变。
  • 这不起作用,因为MovieActorsMovieImages 包含还需要覆盖EqualsGetHashCode 的自定义类型。 GethashCode 实现还应该累积每个图像和演员的GetHashCode,而不是列表。
【解决方案2】:

你可以使用Distinct with EqualityComparer这样的东西

public class MoviesEqualityComparer : IEqualityComparer<Movie>
{
    public bool Equals(Movie x, Movie y)
    {
        return ..../* check all needed fields */

    }

    public int GetHashCode(Movie obj)
    {
        return .... /* get hashcode for movie objec*/
    }
}

并像使用它

List<Movie> myDistinctMovies = myMovies.Distinct(new MoviesEqualityComparer()).ToList(); 

【讨论】:

  • 难点在于实现 ;)
  • @TimSchmelter 是的,如果需要只忽略一个字段。可能使用分组将是最简单的
【解决方案3】:

您可以使用 LINQ 查询可能是...

List<Movie> myDistinctMovies  = (myMovies .GroupBy(p =>p.MovieName,
                                                   p =>p.MovieRating,p=>p.MovieRuntime)
                                          .Select(g => g.First())
                                          .ToList());

谢谢..

【讨论】:

  • 这是我个人的偏好,因为它不需要创建 IEqualityComparer 的实现(糟糕)。
  • 谢谢,我觉得它也更容易和准确!
  • 但这忽略了评级、运行时间、演员和图像。 -1
  • @Tim Schmelter:将其他字段添加到您的组中。我记得多键分组的语法有点古怪,但是您可以找到示例。见:link
  • @Hezi:如果它是单个对象但演员和图像是具有自定义类型的列表,这将起作用。
猜你喜欢
  • 2022-11-30
  • 1970-01-01
  • 2014-01-29
  • 1970-01-01
  • 1970-01-01
  • 2014-01-03
  • 2023-02-26
  • 1970-01-01
相关资源
最近更新 更多