2016-06-15 2 views
1

Рассмотрим следующий пример:OrderBy ThenBy - Самый простой способ поймать оставшиеся (равные) предметы?

new[] { 
    new { A = 3, B = 3 }, 
    new { A = 2, B = 2 }, 
    new { A = 2, B = 2 }, 
    new { A = 1, B = 1 } 
} 
.OrderBy(x => x.A) 
.ThenBy(x => x.B) 
.ToList(); 

Этот List будет содержать следующие данные, в следующем порядке:

[{А = 1, В = 1}, {А = 2, В = 2}, {а = 2, в = 2}, {а = 3, в = 3}]

Как можно определить, что элементы 1 и 2 равны? Я хочу, чтобы мой код был throw, когда все операторы OrderBy/ThenBy выполнили, и есть все еще элементы, которые равны (не могут быть отсортированы).

Примечание, представьте себе, что элементы очень сложны (имеет много свойств), и что есть миллион OrderBy/ThenBy заявление, а также много данных. Я бы предпочел избежать другой итерации данных.

+2

Может быть, вызов на каком-то месте? –

+0

Извините, я уточнил это в моем вопросе сейчас: я ищу что-то, что играет с LINQ, то есть не требует другой итерации данных. – Simeon

ответ

3

Вы можете сделать это до Вы сортируете. Самый простой способ идентифицировать дубликаты - использовать GroupBy.

Поскольку вы используете анонимные типы вы можете группировать сами предметы:

var data = 
    new[] { 
     new { A = 3, B = 3 }, 
     new { A = 2, B = 2 }, 
     new { A = 2, B = 2 }, 
     new { A = 1, B = 1 } 
    }; 

var groups = data.GroupBy(x => x); // works since we are using anonymous types that use value equality 

if(groups.Any(g => g.Count() > 1) 
{ 
    // throw exception 
} 

var result = groups.Select(g=>g.Key) 
        .OrderBy(x => x.A) 
        .ThenBy(x => x.B) 
        .ToList(); 

Если вы не используете анонимные типы в реальности, то просто группы по свойствам, которые вы хотите использовать, чтобы определить " равенство».

Вы также можете проверить для последовательного дубликата «инлайн» с использованием метода расширения:

public static IEnumerable<T> ThrowIfConsecutiveItemsAreEqual<T>(this IEnumerable<T> source) 
{ 
    bool isFirst = true; 
    T prev = default(T); 
    foreach(var item in source) 
    { 
     if(!isFirst && item.Equals(prev)) 
      throw new Exception(); // TODO: use a better exception type and message 

     yield return item; 

     isFirst = false; 
     prev = item; 
    } 
} 

вызывая метод расширения перед темToList, чтобы избежать многочисленных перечислений:

var query = 
    new[] { 
    new { A = 3, B = 3 }, 
    new { A = 2, B = 2 }, 
    new { A = 2, B = 2 }, 
    new { A = 1, B = 1 } 
} 
.OrderBy(x => x.A) 
.ThenBy(x => x.B) 
.ThrowIfConsecutiveItemsAreEqual() 
.ToList(); 
+0

Это увеличивает процедуру с помощью O (n) ... Я ищу лучший способ, что-то вроде 'ThenWithTheRest (...)', но без глупого имени. – Simeon

+0

@Simeon нет перегрузки 'OrderBy', который выдает, если будут найдены дубликаты. Если вы хотите что-то, что делает это _while_, он прикажет, что вам придется его вручную катить. –

+0

@ Симеон Я подумал об этом еще и придумал возможное решение. См. Мое обновление. –

0

Для того, чтобы найти дублирует этот путь, вам нужно только просмотреть предыдущий элемент после сортировки:

static IEnumerable<C> Deduplicate(this IEnumerable<C> items) { 
C last = null; 
foreach (var item in items) { 
    if (last != null && last.A == item.A && last.B == item.B) { 
    //duplicate, handle as you like 
    } 

    last = item; 
    yield return item; 
} 

Существуют различные способы факторизации этой логики, но это основная идея. Мне было полезно написать вспомогательный итератор, который возвращает мне ту же последовательность, но с предыдущим элементом для каждого элемента. С помощью этого помощника вам больше не нужно писать пользовательский итератор.

Смежные вопросы