不同对象类型的 C# 列表:比 Zip 更好的检查相等性的方法

C# List of different object types: Better way than Zip to check equality

我现在有可用的代码,在 lambda 中有效地执行 Linq 的 Zip 扩展方法的元素比较,然后检查是否有任何失败的匹配,但我几乎可以肯定必须有一种更简洁和可读的方式完成同样的事情。

我知道我可以实现一个 EqualityComparer 并使用 SequenceEqual,但我不喜欢像这个这样的一次性列表比较解决方案的开销。我愿意接受 EqualityComparer 是 正确的 解决方案,但如果有其他选择,我很乐意看到它们。

class Occurrence {
    DateTime Start;
    // More fields...
}

bool SameDates(List<Occurrence> occurrences, List<DateTime> selectedDates)
{
    if (occurrences.Count != selectedDates.Count)
            return false;

    // These three lines are the focus of the question
    var dateEqualList = occurrences.Zip(selectedDates, (a, b) => a.Start.Date == b.Date);
    if (dateEqualList.Contains(false))
        return false;

    return true;
}

这些列表有多大?是否可以采取另一种方法并做这样的事情:

class Occurrence {
    DateTime Start;
    // More fields...
}

bool SameDates(List<Occurrence> occurrences, List<DateTime> selectedDates)
{
    if (occurrences.Count != selectedDates.Count)
            return false;
    
    var dates1List = occurrences.Select(o => o.Start).Distinct();
    var dates1String = string.Join(",", dates1List.ToArray());
    
    var dates2List = selectedDates.Select(o => o.Start).Distinct();
    var dates2String = string.Join(",", dates2List.ToArray());
    
    return dates1String == dates2String;
}

即使这不能完全解决或解决您的问题,也许它会给您一些想法。

我写了两个不同的备选方案,第二个有更好的性能。

选项 1:

bool SameDates(List<Occurrence> occurrences, List<DateTime> selectedDates)
{
    if (occurrences.Count != selectedDates.Count)
        return false;

    var matchCount = occurrences.TakeWhile((d, i) => d.Start.Date == selectedDates[i].Date).Count();
    return matchCount == occurrences.Count;
}

选择2:

bool SameDates2(List<Occurrence> occurrences, List<DateTime> selectedDates)
{
    if (occurrences.Count != selectedDates.Count)
        return false;

    for (var i = 0; i < occurrences.Count; i++)
    {
        if (occurrences[i].Start.Date != selectedDates[i].Date)
            return false;
    }

    return true;
}

因此您需要一个 LINQ 语句来检查所有索引的 Occurrences[i].Start 是否等于 selectedDates[i]。您想在发现不相等的序列后立即停止。

我的建议是先 Select 属性 开始,然后检查结果序列是否等于 selectedDates:

List<Occurence> occurences = ...
List<DateTime> selectedDates = ...

var allDatesEqual = occurrences.Count == selectedDates.Count
                 && occurrences.Select(occurrence => occurence.Start)
                               .SequenceEqual(selectedDates);

如果两个序列都实现了 ICollection,通常 SequenceEqual 会检查长度。然而,Select 的结果并未实现 ICollection,因此对于此优化,您需要自己检查计数。

我读到您在开始比较之前对序列进行排序。如果所有日期都是唯一的,请考虑使用 HashSet<DateTime>

List<Occurence> occurences = ...
HashSet<DateTime> datesSet = new HashSet<DateTime>(selectedDates);

var allDatesEqual = datesSet.SetEquals(occurrences
                                       .Select(occurrence => occurence.Start));

SetEquals returns 如果它们包含完全相同的元素,忽略顺序,则为真,因此设置 {A, B, C} 等于设置 {B, C, A},但不等于 {A, B},也不是{A、B、C、D}