19

我在 C# 中有一个 List<> 对象,我需要一种方法来返回那些在列表中被视为重复的对象。我不需要 Distinct 结果集,我需要我将从存储库中删除的那些项目的列表。

为了这个例子,假设我有一个“汽车”类型的列表,我需要知道这些汽车中的哪些与列表中的另一个颜色相同。以下是列表中的汽车及其颜色属性:

Car1.Color = Red;

Car2.Color = Blue;

Car3.Color = Green;

Car4.Color = Red;

Car5.Color = Red;

对于此示例,我需要结果(IEnumerable<>、List<> 或其他)包含 Car4 和 Car5,因为我想从我的存储库或数据库中删除它们,以便我的存储库中每种颜色只有一辆汽车。任何帮助,将不胜感激。

4

8 回答 8

29

我昨天无意中编码了这个,当我试图写一个“通过投影区分”时。我包括一个!当我不应该有的时候,但这次恰到好处:

public static IEnumerable<TSource> DuplicatesBy<TSource, TKey>
    (this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
{
    HashSet<TKey> seenKeys = new HashSet<TKey>();
    foreach (TSource element in source)
    {
        // Yield it if the key hasn't actually been added - i.e. it
        // was already in the set
        if (!seenKeys.Add(keySelector(element)))
        {
            yield return element;
        }
    }
}

然后,您可以使用以下命令调用它:

var duplicates = cars.DuplicatesBy(car => car.Color);
于 2009-01-29T22:21:44.070 回答
17
var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.Skip(1)
                 select car;

这会按颜色对汽车进行分组,然后跳过每个组的第一个结果,将每个组的剩余部分返回到一个序列中。

如果您对要保留哪一个有特殊要求,例如,如果汽车Id有财产并且您想保留最低的汽车Id,那么您可以在其中添加一些排序,例如

var duplicates = from car in cars
                 group car by car.Color into grouped
                 from car in grouped.OrderBy(c => c.Id).Skip(1)
                 select car;
于 2009-01-29T22:27:41.657 回答
5

这是一个稍微不同的 Linq 解决方案,我认为它可以让您更清楚地了解您想要做什么:

var s = from car in cars
    group car by car.Color into g
    where g.Count() == 1
    select g.First();

它只是按颜色对汽车进行分组,丢弃所有具有多个元素的组,然后将其余部分放入返回的 IEnumerable 中。

于 2009-01-30T14:51:25.120 回答
3
IEnumerable<Car> GetDuplicateColors(List<Car> cars)
{
    return cars.Where(c => cars.Any(c2 => c2.Color == c.Color && cars.IndexOf(c2) < cars.IndexOf(c) ) );
}    

它基本上意味着“返回列表中具有相同颜色和较小索引的任何汽车的汽车”。

不过,不确定性能。我怀疑使用 O(1) 查找重复项的方法(如字典/哈希集方法)对于大型集合可能更快。

于 2009-01-29T22:11:00.113 回答
3

创建一个新的Dictionary<Color, Car> foundColors和一个List<Car> carsToDelete

然后你遍历你的原始汽车列表,如下所示:

foreach(Car c in listOfCars)
{
    if (foundColors.containsKey(c.Color))
    {
        carsToDelete.Add(c);
    }
    else
    {
        foundColors.Add(c.Color, c);
    }
}

然后你可以删除 foundColors 中的每辆车。

您可以通过将“删除记录”逻辑放入if语句中而不是创建新列表来获得较小的性能提升,但是您对问题的措辞表明您需要将它们收集到一个列表中。

于 2009-01-29T22:11:23.843 回答
0

如果没有实际编码,那么像这样的算法怎么样:

  • 遍历您List<T>创建的Dictionary<T, int>
  • 遍历您的Dictionary<T, int>删除条目int> 1

剩下的任何东西Dictionary都有重复。当然,您实际删除的第二部分是可选的。您可以遍历Dictionary并寻找 >1 采取行动。

编辑:好的,我提高了 Ryan 的,因为他实际上给了你代码。;)

于 2009-01-29T22:12:16.960 回答
0

我的回答从追随者受访者那里得到灵感(按此顺序):Joe Coehoorn、Greg Beech 和 Jon Skeet。

我决定提供一个完整的例子,假设(为了实际效率)你有一个静态的汽车颜色列表。我相信以下代码以一种优雅但不一定超高效的方式说明了该问题的完整解决方案。

#region SearchForNonDistinctMembersInAGenericListSample
public static string[] carColors = new[]{"Red", "Blue", "Green"}; 
public static string[] carStyles = new[]{"Compact", "Sedan", "SUV", "Mini-Van", "Jeep"}; 
public class Car
{
    public Car(){}
    public string Color { get; set; }
    public string Style { get; set; }
}
public static List<Car> SearchForNonDistinctMembersInAList()
{
    // pass in cars normally, but declare here for brevity
    var cars = new List<Car>(5) { new Car(){Color=carColors[0], Style=carStyles[0]}, 
                                      new Car(){Color=carColors[1],Style=carStyles[1]},
                                      new Car(){Color=carColors[0],Style=carStyles[2]}, 
                                      new Car(){Color=carColors[2],Style=carStyles[3]}, 
                                      new Car(){Color=carColors[0],Style=carStyles[4]}};
    List<Car> carDupes = new List<Car>();

    for (int i = 0; i < carColors.Length; i++)
    {
        Func<Car,bool> dupeMatcher = c => c.Color == carColors[i];

        int count = cars.Count<Car>(dupeMatcher);

        if (count > 1) // we have duplicates
        {
            foreach (Car dupe in cars.Where<Car>(dupeMatcher).Skip<Car>(1))
            {
                carDupes.Add(dupe);
            }
        }
    }
    return carDupes;
}
#endregion

我稍后会回到这里,并将这个解决方案与它的所有三个灵感进行比较,只是为了对比风格。这很有趣。

于 2009-01-30T01:48:07.867 回答
0

公共静态 IQueryable Duplicates(此 IEnumerable 源)其中 TSource : IComparable {

if (source == null)   
     throw new ArgumentNullException("source");   
 return source.Where(x => source.Count(y=>y.Equals(x)) > 1).AsQueryable<TSource>();   

}

于 2009-01-30T15:02:42.380 回答