2013-12-17 6 views
2

Я создал класс, как показано ниже, для представления составной первичного ключа модели:Определение уникальных значений в C# Списка

public class PrimaryKeyModel 
{ 
    public string ColumnName { get; set; } 
    public string ColumnValue { get; set; } 
    public int RowNumber { get; set; } // always unique 
} 

Это в основном отражающих имена/значение столбцов, которые вместе составляют первичные key, плюс номер строки, в которой эта комбинация принадлежит; первоначально в Таблице.

меня тогда поставить эту модель в список и заселили ее с данными из таблицы:

List<PrimaryKeyModel> primaryKeysList = new List<PrimaryKeyModel>; 

Я хотел бы проверить primaryKeysList и посмотреть, если он имеет какие-либо дублирующие значения, и если у него есть, я хотел бы знать номера строк, где эти значения дублируются.

Я пробовал разные способы, такие как загрузка этого списка в HashSet, словарь и использование this solution here at this link, но не работало. В любом случае я могу это разрешить.

Спасибо.

Обновление - вот пример отображения данных. UniqueColumnsModel - это то же самое, что и PrimaryKeyModel; Я изменил его здесь, чтобы сделать его более ясным.

enter image description here

Edit:. Разъяснение вопроса

Я пытаюсь импортировать данные из электронной таблицы (которая может иметь много типов (один для продаж, один для котировок ..etc)) в базу данных. Таблица конфигурации в базе данных определяет, какой столбец (ы) в электронной таблице будет составлять первичный ключ в таблице назначения. Моя задача - создать подпрограмму, которая проверяет данные электронной таблицы перед ее загрузкой (импортированием) в базу данных с помощью моего приложения. Я хочу, чтобы не проверять, что столбцы, установленные как составные части первичного ключа, не содержат дублированных данных, так что ограничение первичного ключа НЕ нарушено в таблице адресатов при вставке.

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

+0

Это действительно хороший шанс использовать BinarySearch в списке, передавая собственный компаратор для PrimaryKeyModel. BinarySearch возвращает те, которые дополняют результаты, указывающие индекс, в котором этот элемент существует. – Haney

+1

@DavidHaney Прежде всего, двоичный поиск предназначен для поиска одного элемента, а не для поиска дубликатов, во-вторых, для этого требуется сортировка данных, что, похоже, не так. – Servy

+0

@Servy, но я имел в виду, что он мог использовать его во время добавления в список, чтобы проверить дубликат при добавлении ... Одиночный обход данных O (n) с двоичным поиском O (log n) для каждого элемента. – Haney

ответ

2

Если ваш класс представляет этот вид структуры:

ColumnName ColumnValue RowNumber 
Id   3    1 
Id2   1    1 
Id   1    2 
Id2   2    2 
Id   3    3 
Id2   1    3 //duplicate 

Тогда все другие ответы до сих пор являются неправильными и что вам нужно сделать это по-другому, группа по номеру строки, а затем сравнить каждое поле по одному. Поскольку равенство равно commutative, мы можем немного ускорить цикл, чтобы мы не сравнивали каждый элемент дважды.

List<PrimaryKeyModel> keys = new List<PrimaryKeyModel>() 
{ 
     new PrimaryKeyModel("Id", "3", 1), 
     new PrimaryKeyModel("Id2", "1", 1), 
     new PrimaryKeyModel("Id", "1", 2), 
     new PrimaryKeyModel("Id2", "1", 2), 
     new PrimaryKeyModel("Id", "3", 3), 
     new PrimaryKeyModel("Id2", "1", 3), 
}; 

var groupedKeys = keys.OrderBy(pk => pk.ColumnName).GroupBy(k => k.RowNumber).ToList(); 
HashSet<int> duplicateRowNumbers = new HashSet<int>(); 

for (int i = 0; i < groupedKeys.Count - 1; i++) 
{ 
    for (int j = i + 1; j < groupedKeys.Count; j++) 
    { 
     if (AreTheSame(groupedKeys[i], groupedKeys[j])) 
     { 
      duplicateRowNumbers.Add(groupedKeys[i].First().RowNumber); 
      duplicateRowNumbers.Add(groupedKeys[j].First().RowNumber); 
     } 
    } 
} 

private static bool AreTheSame(IEnumerable<PrimaryKeyModel> a, IEnumerable<PrimaryKeyModel> b) 
{ 
    var leftEnumerator = a.GetEnumerator(); 
    var rightEnumerator = b.GetEnumerator(); 
    while (leftEnumerator.MoveNext() | rightEnumerator.MoveNext()) 
    { 
     if (leftEnumerator.Current == null) return false; 
     if (rightEnumerator.Current == null) return false; 
     if (leftEnumerator.Current.ColumnValue != rightEnumerator.Current.ColumnValue) return false; 
    } 

    return true; 
} 
+0

Большое вам спасибо. Это сработало! :) @Bas Brekelmans –

+0

При дальнейшем тестировании с различными шаблонами данных я обнаружил, что это решение терпит неудачу, когда «комбинация» столбцов имеет повторяющиеся значения. Он может работать с некоторой настройкой, поскольку в настоящее время он проверяет каждый столбец индивидуально, а не коллективно. для каждой итерации в строке данных все столбцы вместе ДОЛЖНЫ дать уникальное значение. В любом случае, я поставил свое решение ниже. @Bas Brekelmans –

+0

Я думаю, что может возникнуть проблема с вашим объяснением, мое решение сортирует столбцы по имени и возвращает только true, если все пары значений столбцов в двух номерах строк имеют точно такое же значение. @t_plusplus – Bas

2

РЕДАКТИРОВАТЬ: Возможно, я неправильно понял вопрос и сделал вывод, что ваше имя класса PrimaryKeyModel. Я интерпретировал это как модель для первичного ключа и что вы хотели найти повторяющиеся первичные ключи. Если это не так, я настоятельно рекомендую вам пересмотреть свое имя ... в этот момент, ответ D Stanley , вероятно,, что вы хотите, но вы должны считать ColumnName/ColumnValue «основным ключом» здесь - номер строки не часть ключа, логически.


Оригинальный ответ

Вы, кажется, не были переопределены Equals(object) или GetHashCode - что означает, что каждый объект считается отличается от любой другой.Вы, наверное, хотите что-то вроде:

public sealed class PrimaryKeyModel : IEquatable<PrimaryKeyModel> 
{ 
    // TODO: Make these read-only (mutable keys are a bad idea...) 
    public string ColumnName { get; set; } 
    public string ColumnValue { get; set; } 
    public int RowNumber { get; set; } 

    public override bool Equals(object other) 
    { 
     return Equals(other as PrimaryKeyModel); 
    } 

    public bool Equals(PrimaryKeyModel other) 
    { 
     return other != null && 
       ColumnName == other.ColumnName && 
       ColumnValue == other.ColumnValue && 
       RowNumber == other.RowNumber; 
    } 

    public override int GetHashCode() 
    { 
     int hash = 23; 
     hash = hash * 31 + ColumnName == null ? 0 : ColumnName.GetHashCode(); 
     hash = hash * 31 + ColumnValue == null ? 0 : ColumnValue.GetHashCode(); 
     hash = hash * 31 + RowNumber; 
     return hash; 
    } 
} 

Это предполагает, что вы действительно хотите, все три поля, чтобы быть такой же - если вы только забота о RowNumber, вы можете упростить эти реализации (но на тот момент это нечетное первичная ключ).

После этого, вы можете использовать Distinct(), или HashSet, или Dictionary и т.д. Конечно, альтернативой является явно группировать по различным свойствам - но он чувствует, как этот должен осуществить равенство здраво. Как отмечено в комментариях, я настоятельно призываю вас сделать свойства только для чтения.

+1

Большая реализация 'Equals' /' GetHashCode', но вопрос требует найти дубликаты _name/value_ pais и _row numbers_ этих дубликатов. –

+0

@DStanley: Хмм ... Думаю, я вижу, и в этом случае 'PrimaryKeyModel' является * ужасным * именем для класса. Пока неясно, - отредактирует. –

4

GroupBy хорошо работает для этого:

primaryKeysList.GroupBy(pk => new {pk.ColumnName, pk.ColumnValue}) 
       .Where(g => g.Count() > 1) 
       .SelectMany(g => g); // flatten the groups into a single list 
+0

Это дает много ложных срабатываний, если вы посмотрите на обновленный вопрос. – Bas

0

Это было окончательное решение, которое сработало для меня. Это гарантирует, что дубликаты не существуют в строке списка, т. Е. Списка списка. Он в основном переливает содержимое списка в hashset, который возвращает false, если недавно добавленный элемент уже существует в списке:

Спасибо всем, кто внес свой вклад в решение этого выше!

HashSet<string> primaryKeyChecker = new HashSet<string>(); 

foreach (var row in rows) 
{ 

    StringBuilder primaryKey = new StringBuilder(); 
    //Get rowCount; 

    foreach (var column in columns) 
    { 
     (if column is a composite of a primaryKey) 
     { 
      get column value; 
      append it to stringBuilder to form the primaryKey 
     } 
    } 

          var addOutcome = primaryKeyChecker.Add(primaryKey.ToString()); 

          if (!addOutcome) 
          { 
           //Report a duplicate record and give the rowNumber where this occured. 
          } 


} 

Update

Чтобы выйти из вопроса, выделены @Bas ниже, просто убедитесь, что при конкатенации первичных ключей; чтобы разделить их с комой или 0, так что выделенный сценарий обыкновение occurre .. так что-то вроде этого:

primaryKey.Append(currentValue + ","); 
+0

это сломается с помощью ввода (21, 2) и (2, 12), потому что вы объединяете строки и сообщаете о дубликате. – Bas

+0

Спасибо, что выделили этот вопрос для меня, я поставил для него резолюцию в обновлении выше @Bas Brekelmans –

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