Запрос LINQ для обнаружения повторяющихся свойств в списке объектов

У меня есть список объектов. Эти объекты состоят из настраиваемого класса, который в основном содержит два строковых поля String1 и String2.

Мне нужно знать, дублируются ли какие-либо из этих строк в этом списке. Итак, я хочу знать, objectA.String1 == objectB.String1, ObjectA.String2 == ObjectB.String2, ObjectA.String1 == ObjectB.String "или ObjectA.String2 == ObjectB.String1.

Кроме того, я хочу пометить каждый объект, содержащий повторяющуюся строку, как имеющий повторяющуюся строку (с bool HasDuplicate на объекте).

Итак, когда обнаружено дублирование, я хочу просто выполнить foreach по списку следующим образом:

foreach (var item in duplicationList)
    if (item.HasDuplicate)
        Console.WriteLine("Duplicate detected!");

Это казалось хорошей проблемой для решения с помощью LINQ, но я не могу, хоть убей, найти хороший запрос. Итак, я решил это с помощью «старого доброго» foreach, но меня все еще интересует версия LINQ.


person Jeroen-bart Engelen    schedule 16.12.2009    source источник


Ответы (5)


Вот полный пример кода, который должен работать в вашем случае.

class A
{
    public string Foo   { get; set; }
    public string Bar   { get; set; }
    public bool HasDupe { get; set; }
}

var list = new List<A> 
          { 
              new A{ Foo="abc", Bar="xyz"}, 
              new A{ Foo="def", Bar="ghi"}, 
              new A{ Foo="123", Bar="abc"}  
          };

var dupes = list.Where(a => list
          .Except(new List<A>{a})
          .Any(x => x.Foo == a.Foo || x.Bar == a.Bar || x.Foo == a.Bar || x.Bar == a.Foo))
          .ToList();

dupes.ForEach(a => a.HasDupe = true);
person Winston Smith    schedule 16.12.2009
comment
LINQPad - отличный инструмент для решения подобных проблем - у каждого разработчика C # должна быть копия. - person Winston Smith; 16.12.2009
comment
Хороший. Только один момент - я бы подумал, что перемещение логики из Except в метод Any было бы немного более эффективным, потому что это сохранит создание списка для каждого проверяемого элемента, например var dupes = list.Where (a = ›list .Any (a! = x && (x =› x.Foo == a.Foo || x.Bar == a.Bar || x.Foo == a. Бар || x.Bar == a.Foo))) .ToList (); - person Mike Goatly; 28.08.2012
comment
Имейте в виду, что пустые строки также равны друг другу, поэтому проверьте это, если вы хотите игнорировать пустые строки. - person CAD bloke; 05.05.2015
comment
Пожалуйста, удалите метод расширения ForEach. Это обескуражено и больше не существует. - person SuperJMN; 06.11.2015

Это должно работать:

public class Foo
{
    public string Bar;
    public string Baz;
    public bool HasDuplicates;
}

public static void SetHasDuplicate(IEnumerable<Foo> foos)
{
    var dupes = foos
        .SelectMany(f => new[] { new { Foo = f, Str = f.Bar }, new { Foo = f, Str = f.Baz } })
        .Distinct() // Eliminates double entries where Foo.Bar == Foo.Baz
        .GroupBy(x => x.Str)
        .Where(g => g.Count() > 1)
        .SelectMany(g => g.Select(x => x.Foo))
        .Distinct()
        .ToList();

    dupes.ForEach(d => d.HasDuplicates = true);    
}

В основном вы делаете

  1. SelectMany: создать список всех строк с сопровождающим их Foo
  2. Отдельно: удалить двойные записи для одного и того же экземпляра Foo (Foo.Bar == Foo.Baz)
  3. GroupBy: Группировка по строке
  4. Где: фильтровать группы, в которых содержится более одного элемента. Они содержат дубликаты.
  5. SelectMany: вернуть фу из групп.
  6. Distinct: удалить из списка двойное вхождение foo.
  7. ForEach: установите свойство HasDuplicates.

Некоторые преимущества этого решения перед решением Уинстона Смита:

  1. Легче расширить на большее количество строковых свойств. Предположим, было 5 объектов недвижимости. В его решении вам нужно будет написать 125 сравнений для проверки дубликатов (в предложении Any). В этом решении просто нужно добавить свойство при первом вызове selectmany.
  2. Производительность должна быть намного лучше для больших списков. Решение Winston выполняет итерацию по списку для каждого элемента в списке, в то время как это решение выполняет итерацию по нему только один раз. (Решение Уинстона - O (n²), а это - O (n)).
person Geert Baeyaert    schedule 16.12.2009
comment
Grouping lazy оценивает своих членов группы? g.Skip (1) .Any () может быть улучшением по сравнению с g.Count () ›1 - person Jimmy; 17.12.2009
comment
@Jimmy В данном случае это не имеет особого значения, потому что группы не лениво оцениваются. Мне нравится трюк Skip (1) .Any (). Для моих собственных проектов у меня всегда есть методы расширений CountIs (ожидаемое число int), CountIsGreaterThan (ожидаемое число int) ... которые прекращают оценку, как только узнают ответ. - person Geert Baeyaert; 17.12.2009

Во-первых, если у вашего объекта еще нет свойства HasDuplicate, объявите метод расширения, реализующий HasDuplicateProperties:

public static bool HasDuplicateProperties<T>(this T instance)
    where T : SomeClass 
    // where is optional, but might be useful when you want to enforce
    // a base class/interface
{
    // use reflection or something else to determine wether this instance
    // has duplicate properties
    return false;
}

Вы можете использовать этот метод расширения в запросах:

var itemsWithDuplicates = from item in duplicationList
                          where item.HasDuplicateProperties()
                          select item;

То же самое работает с обычным свойством:

var itemsWithDuplicates = from item in duplicationList
                          where item.HasDuplicate
                          select item;

or

var itemsWithDuplicates = duplicationList.Where(x => x.HasDuplicateProperties());
person Sander Rijken    schedule 16.12.2009
comment
Это не мой вопрос. Я хотел знать, как определить, есть ли у меня дубликат, чтобы установить значение bool. Когда установлено значение bool, я знаю, как получить все объекты из списка, в котором он установлен. - person Jeroen-bart Engelen; 16.12.2009

Совет для https://stackoverflow.com/a/807816/492

var duplicates = duplicationList
                .GroupBy(l => l)
                .Where(g => g.Count() > 1)
                .Select(g => {foreach (var x in g)
                                 {x.HasDuplicate = true;}
                             return g;
                });

duplicates - это одноразовый продукт, но он поможет вам достичь этого за меньшее количество перечислений.

person CAD bloke    schedule 07.04.2016

person    schedule
comment
Я протестировал ваш код в LINQPad с помощью следующей программы: void Main () {var duplicationList = new List ‹TestObject› {new TestObject (1, 2), new TestObject (3, 4), new TestObject (1, 6) }; var dups = duplicationList.GroupBy (x = ›x) .Where (y =› y.Count () ›1) .Select (y =› y.Key); dups.Dump (Дублирующий дамп: + dups.Count ()); } открытый класс TestObject {общедоступный TestObject (строка s1, строка s2) {String1 = s1; String2 = s2; IsDuplicate = false; } публичная строка String1; публичная строка String2; public bool IsDuplicate; } Это не работает. dups содержит 0 значений. - person Jeroen-bart Engelen; 16.12.2009