【问题标题】:Compare sublist of a list for objects that change, in Linq在 Linq 中比较更改的对象列表的子列表
【发布时间】:2013-06-21 21:41:22
【问题描述】:

我有一个包含字符串子列表的对象列表,这些结构可以在几天之间更改,我希望比较它们以查看是否进行了更改。

public class Recipe
{
    public string ID { get; set; }
    public string Name { get; set; }
    public List<string> Ingredients { get; set; }
} 

ID 字段在列表版本之间是相同的,Ingredients 只是 字符串的列表。

List<Recipe> list1 = GetRecipes("2013-06-20");
List<Recipe> list2 = GetRecipes("2013-06-21");

我正在尝试查找所有在几天之间发生成分变化的Recipes。我已经能够想出一个 Linq 语句来找到新的 Recipes 是在 list2 但不是 list1 像这样

var newRecipes = list1.Where(x => !list2.Any(x1 => x1.ID == x.ID))
    .Union(list2.Where(x => !list1.Any(x1 => x1.ID == x.ID)));

但是,我还没有弄清楚如何只选择列表之间 Ingredient 发生变化的 Recipes。

var modifiedRecipes = list1.Where(x => !list2.Any(x1 => x1.ID == x.ID && x1.Ingedients.SequenceEqual(x.Ingedients)))
    .Union(list2.Where(x => !list1.Any(x1 => x1.ID == x.ID && x1.Ingedients.SequenceEqual(x.Ingedients))));

如何获取在字符串子列表中发生更改的对象列表?

【问题讨论】:

    标签: c# linq list


    【解决方案1】:

    此代码将为您提供具有相同 ID 的每个不匹配的收据对。您必须对成分列表进行排序才能使 SequenceEqual 起作用。

    var changed = from older in GetRecipes("2013-06-20")
                  join newer in GetRecipes("2013-06-21") on older.ID equals newer.ID
                  where !older.Ingredients.SequenceEquals(newer.Ingredients)
                  select new { older, newer };
    

    【讨论】:

    • 当有更好的选择时,这是非常低效的。
    • @newStackExchangeInstance 实际上,它比您的效率更高,因为您不会首先检查 ID 是否相等以消除具有不同 ID 的收件人。您的解决方案将运行更多 SequenceEquals (更多取决于 ExceptBy 的实现,这是不必要的第 3 方代码)
    • ExceptBy 是 Jon Skeet 的代码。 /programmingmeme 实际上,我的是 ~O(m+n),因为 exceptBy 在内部使用哈希集,因此只检查是否存在哈希桶冲突。你的有点像 O(mn) 和更多。
    • @newStackExchangeInstance 即使在我提出修复后,您的解决方案的性能也比我的差,this code 证明了这一点。我的解决方案的复杂性远不及您声称的 O(nm)。请注意,我首先对 id 执行连接,这是使用 hashet 完成的,复杂度为 O(n+m)。然后对于每对具有相同 ID 的元素,我检查序列是否相等。
    • 真的吗?此外,如果您尝试优化我的代码(您没有),您不仅会意识到哈希集依赖于素数可除性的某种一致性,其中一些是 17 和 13。此外,与 foreach 循环相比,Aggregate 很慢.另外,不要在循环的每次运行中实例化一个新的EnumerableComparer。这样,我的解决方案就更快了。在这里查看我的代码:pastebin.com/SenZA8S7
    【解决方案2】:

    当然,你可以这样做。首先获取MoreLINQ(没有它也可以完成,但是效率稍低,请问我是否需要它)。不过,在此之前我们需要添加一个更好的可枚举IEqualityComparer:

    public class EnumerableComparer<T> : IEqualityComparer<IEnumerable<T>>
    {
        public void Equals(IEnumerable<T> x, IEnumerable<T> y)
        {
            return x.SequenceEqual(y);
        }
        public int GetHashCode(IEnumerable<T> obj)
        {
            int x = 31;
            foreach (T element in obj)
            {
                x += 37 * element.GetHashCode();
            }
            return x;
        }
    }
    

    之后就这么简单:

    var modifiedRecipes = list2.ExceptBy(list1, n => n.Ingredients,
        new EnumerableComparer<string>());
    

    如果你打算在你的代码中使用它,我不会一直实例化一个新的 EnumerableComparer,因为它们都是一样的。对每种类型使用相同的实例。

    【讨论】:

    • 感谢您的帮助。这似乎返回了 list1 中的所有Recipes,而不是那些改变的。我应该做任何额外的比较吗?
    • 这个答案不正确。 GetHashCode 在不同的 Ingredients 对象上总是不同的,因为它只检查引用相等性(因此永远不会调用 Equals)。您可以通过使用例如 return obj.Aggregate( 13, (acc, item) =&gt; acc * 17 + item.GetHashCode()); 来解决这个问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多