【问题标题】:C# Remove duplicated objects from list and increase firstC#从列表中删除重复的对象并首先增加
【发布时间】:2022-01-09 09:18:34
【问题描述】:

我有一个对象列表 => class Example { int quantity; string name; string comment; },我想删除所有重复项并将 quantity 增加具有相同 namecomment 的重复项的数量。

例子:

[
    {quantity: 1, name: "Hello", comment: "Hello there"},
    {quantity: 2, name: "Bye", comment: "Good bye"},
    {quantity: 1, name: "Hi", comment: "Hi there"},
    {quantity: 1, name: "Hello", comment: "Hello there"},
    {quantity: 1, name: "Bye", comment: "Good bye"},
]

结果应该是:

[
    {quantity: 2, name: "Hello", comment: "Hello there"},
    {quantity: 3, name: "Bye", comment: "Good bye"},
    {quantity: 1, name: "Hi", comment: "Hi there"}
]

【问题讨论】:

  • 你尝试了什么,结果如何?
  • GroupBy 名称/注释组合,然后选择组键属性和数量的 Sum()。
  • 请注意,您的示例不是 C#。这是一个 json 问题,还是只是一个例子?
  • 是的,只是一个例子。

标签: c# linq


【解决方案1】:

我想删除所有重复项

您尚未定义两个示例对象何时“重复”。我猜,您的意思是,如果两个示例对象的属性NameComment 具有相同的值,那么它们就是重复的。

通常您可以使用Enumerable.GroupBy 的重载之一来查找重复项。使用带有参数resultSelector 的重载来准确定义您想要的结果。

IEnumerable<Example> examples = ...
var result = examples.GroupBy(

    // key: Name-Comment
    example => new
    {
        Name = example.Name,
        Comment = example.Comment,
    }

    // parameter resultSelector: for every Name/Comment combination and all
    // Examples with this Name/Comment combination make one new example:
    (nameCommentCombination, examplesWithThisNameCommentCombination) => new Example
    {
         Name = nameCommentCombination.Name,
         Comment = nameCommentCombination.Comment,

         // Quantity is the sum of all Quantities of all Examples with this
         // Name/Comment combination
         Quantity = examplesWithThisNameCommentCombination
                    .Select(example => example.Quantity)
                    .Sum(),
    });

这仅在您想要精确的字符串相等时才有效。 “你好”和“你好”是否相等?那么“Déjà vu”和“Deja vu”呢?您想要名称和评论不区分大小写吗?那么变音字符呢?

如果您想要的不仅仅是简单的字符串相等,请考虑创建一个 ExampleComparer 类。

class ExampleComparer : EqualityComparer<Example>
{
    ... // TODO: implement
}

用法如下:

IEnumerable<Example> examples = ...
IEqualityComparer<Example> comparer = ...

var result = examples.GroupBy(example => example,  // key

    // resultSelector:
    (key, examplesWithThisKey) => new Example
    {
         Name = key.Name,
         Comment = key.Comment,
         Quantity = examplesWithThiskey.Sum(example => example.Quantity),
    },

    comparer);

实现 ExampleComparer

class ExampleComparer : EqualityComparer<Example>
{
    public static IEqualityComparer<Example> ByNameComment {get;} = new ExampleComparer;

    private static IEqualityComparer<string> NameComparer => StringComparer.CurrentCultureIgnoreCase;
    private static IEqualityComparer<string> CommentComparer => StringComparer.CurrentCultureIgnoreCase;

我选择了两个单独的字符串比较器,所以如果以后你决定不同的比较,例如名称必须完全匹配,那么你只需要在这里更改它。

public override bool Equals (Example x, Example y)
{
    // almost every Equals method starts with the following three lines
    if (x == null) return y == null;                // true if both null
    if (y == null) return false;                    // false, because x not null
    if (Object.ReferenceEquals(x, y)) return true;  // same object

    // return true if both examples are considered equal:
    return NameComparer.Equals(x.Name, y.Name)
        && CommentComparer.Equals(x.Comment, y.Comment);
}

public override int GetHashCode(Example x)
{
     if (x == null) return 5447125;       // just a number

     return NameComparer.GetHashCode(x.Name)
          ^ CommentComparer.GetHashCode(x.Comment);
}

注意:如果 Name 或 Comment 为 null 或为空,这也将起作用!

我使用了operator ^ (XOR),因为如果只有两个字段需要考虑,它会提供相当好的哈希值。如果您认为绝大多数示例具有唯一名称,请考虑仅检查属性名称:

return NameComparer.GetHashCode(x.Name);

因为方法 Equals 使用 NameComparer 和 CommentComparer 来检查相等性,所以请确保使用相同的 Comparer 来计算 HashCode。

【讨论】:

【解决方案2】:

这是我要做的:

Example[] before = new Example[]
{
    new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
    new Example { Quantity = 2, Name = "Bye", Comment = "Good bye" },
    new Example { Quantity = 1, Name = "Hi", Comment = "Hi there" },
    new Example { Quantity = 1, Name = "Hello", Comment = "Hello there" },
    new Example { Quantity = 1, Name = "Bye", Comment = "Good bye" },
};

Example[] after =
    before
        .GroupBy(x => new { x.Name, x.Comment }, x => x.Quantity)
        .Select(x => new Example { Quantity = x.Sum(), Name = x.Key.Name, Comment = x.Key.Comment })
        .ToArray();

这给了:


也许这个版本更清晰一点:

Example[] after =
    before
        .GroupBy(
            x => new { x.Name, x.Comment },
            (k, xs) => new Example
            {
                Quantity = xs.Sum(x => x.Quantity),
                Name = k.Name,
                Comment = k.Comment
            })
        .ToArray();

或者这样:

Example[] after =
(
    from x in before
    group x.Quantity by new { x.Name, x.Comment } into xs
    select new Example
    {
        Quantity = xs.Sum(x => x),
        Name = xs.Key.Name,
        Comment = xs.Key.Comment
    }
).ToArray();

【讨论】:

  • 太棒了,你能不能在组部分解释一下, f =&gt; f.Quantity 不是这个级别的键的一部分,它在列表/数组中,在选择部分Quantity = x.Sum()它是怎么知道的数量总和
  • .GroupBy(x =&gt; new { x.Name, x.Comment }, x =&gt; x.Quantity)group x.Quantity by new { x.Name, x.Comment } 相同。所以每个组只是@​​987654330@ 的可枚举,因此x.Sum() 有效。
【解决方案3】:

这是一个简单的解决方案,它在 List tata 中为您提供答案,但您可以根据需要执行 .ToArray()。

    public class Example 
    { 
        public int quantity; 
        public string name; 
        public string comment; 
    }

    Example[] toto = new Example[]
    {
        new Example
        {
            quantity = 1,
            name = "Hello",
            comment = "Hello there"
        },
        new Example
        {
            quantity = 2,
            name = "Bye",
            comment = "Good bye"
        },
        new Example
        {
            quantity = 1,
            name = "Hi",
            comment = "Hi there"
        },
        new Example
        {
            quantity = 1,
            name = "Hello",
            comment = "Hello there"
        },
         new Example
        {
            quantity = 1,
            name = "Bye",
            comment = "Good bye"
        }
    };

    List<Example> tata = new List<Example>();
    foreach (Example exa in toto)
    {
        bool found = false;
        foreach (Example exb in tata)
        {
            if (exb.name == exa.name && exb.comment == exa.comment)
            {
                exb.quantity += exa.quantity;
                found = true;
                break;
            }
        }
        if (!found)
        {
            tata.Add(exa);
        }
    }

LINQ 是一个很好的练习!

【讨论】:

  • Euww,嵌套循环.. 字典请
  • @CaiusJard 嵌套循环有什么问题?你能详细说明一下吗?你会如何处理字典?我们这里有三个条目,而不是两个。
  • 嵌套循环有什么问题 - 接近 O(n²).. 你会如何处理字典? - 将键设为 @ 987654322@或(name, comment)或记录; AT、ValueTuples 和记录在其所有组成道具的平等/散列中建立了
  • @CaiusJard 谢谢!在内部,不是所有的解决方案都等同于 2 个嵌套循环吗?我的意思是,做 LINQ 或 DICT 需要时间,不是吗?
  • 这可能取决于,但通常答案是“否”。当列表查找某些内容时,它会从头开始检查每个项目。当字典找到某物时,它会计算它期望该项目在哪里,然后去那里查看该项目;如果它不是正确的,则有一个指向另一项的指针(您的公主在另一座城堡中),这可能是正确的。如果它们是食谱书,那么列表就像“一页一页地寻找双层巧克力蛋糕,在 96 次尝试失败后在第 97 页找到它”,而字典就像..
猜你喜欢
  • 1970-01-01
  • 2012-01-09
  • 2018-03-11
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 2015-08-02
  • 1970-01-01
相关资源
最近更新 更多