Простая ситуация. У меня есть список списков, почти как таблица, и я пытаюсь выяснить, дублируется ли какой-либо из списков.
Пример:
List> list = new List>(){
new List() {0 ,1 ,2, 3, 4, 5, 6 },
new List() {0 ,1 ,2, 3, 4, 5, 6 },
new List() {0 ,1 ,4, 2, 4, 5, 6 },
new List() {0 ,3 ,2, 5, 1, 6, 4 }
};
Я хотел бы знать, что всего 4 элемента, 2 из которых являются дубликаты. Я думал о том, чтобы сделать что-то вроде контрольной суммы SQL , но я не знал, существует ли лучший / более простой способ.
Я забочусь о производительности, и я забочусь о заказе.
Дополнительная информация, которая может помочь
Давайте попробуем добиться максимальной производительности. если n - количество списков, а m - длина списков, то мы можем получить O (n m + n logn + n) плюс некоторая вероятность совпадения хэш-кодов для разных списков.
Основные шаги:
* это важный шаг. для простоты вы можете вычислить хеш как = ... ^ (list [i] << i) ^ (list [i + 1] << (i + 1))
Edit для тех людей, которые думают, что PLINQ может улучшить ситуацию, но не очень хороший алгоритм. Здесь также можно добавить PLINQ, потому что все шаги легко распараллелить.
Мой код:
static public void Main()
{
List<List<int>> list = new List<List<int>>(){
new List<int>() {0 ,1 ,2, 3, 4, 5, 6 },
new List<int>() {0 ,1 ,2, 3, 4, 5, 6 },
new List<int>() {0 ,1 ,4, 2, 4, 5, 6 },
new List<int>() {0 ,3 ,2, 5, 1, 6, 4 }
};
var hashList = list.Select((l, ind) =>
{
uint hash = 0;
for (int i = 0; i < l.Count; i++)
{
uint el = (uint)l[i];
hash ^= (el << i) | (el >> (32 - i));
}
return new {hash, ind};
}).OrderBy(l => l.hash).ToList();
//hashList.Sort();
uint prevHash = hashList[0].hash;
int firstInd = 0;
for (int i = 1; i <= hashList.Count; i++)
{
if (i == hashList.Count || hashList[i].hash != prevHash)
{
for (int n = firstInd; n < i; n++)
for (int m = n + 1; m < i; m++)
{
List<int> x = list[hashList[n].ind];
List<int> y = list[hashList[m].ind];
if (x.Count == y.Count && x.SequenceEqual(y))
Console.WriteLine("Dupes: {0} and {1}", hashList[n].ind, hashList[m].ind);
}
}
if (i == hashList.Count)
break;
if (hashList[i].hash != prevHash)
{
firstInd = i;
prevHash = hashList[i].hash;
}
}
}
Вы также можете попробовать вероятностные алгоритмы, если дубликаты встречаются очень редко или очень часто. например a фильтр цветения
, если все они однозначные и имеют одинаковое количество элементов, вы можете сложить их вместе, так что первым будет 123456 и проверьте, совпадают ли числа.
тогда у вас будет список {123456, 123456, 142456, 325164}
, который легче проверять на дубликаты, если отдельных членов может быть больше 10, вам придется его изменить.
Edit: добавлен пример кода, можно оптимизировать, это просто быстрый пример, объясняющий, что я имел в виду.
for(int i = 0; i< list.length; i++)
{
List<int> tempList = list[i];
int temp = 0;
for(int j = tempList.length - 1;i > = 0; j--)
{
temp = temp * 10 + tempList[j];
}
combinded.add(temp);
}
for(int i =0; i< combined.length; i++)
{
for(int j = i; j < combined.length; j++)
{
if(combined[i] == combined[j])
{
return true;
}
}
}
return false;
Ознакомьтесь с C # 3.0: необходимость возврата дубликатов из списка <> показывает, как вернуть дубликаты из списка.
Пример с этой страницы:
var duplicates = from car in cars
group car by car.Color into grouped
from car in grouped.Skip(1)
select car;
Вам придется выполнить итерацию по каждому индексу каждого списка по крайней мере один раз, но потенциально вы можете ускорить процесс, создав пользовательскую хеш-таблицу, чтобы вы могли быстро отклонять неповторяющиеся списки. без необходимости делать сравнения по элементам.
Алгоритм:
Create a custom hashtable (dictionary: hash -> list of lists)
For each list
Take a hash of the list (one that takes order into account)
Search in hashtable
If you find matches for the hash
For each list in the hash entry, re-compare the tables
If you find a duplicate, return true
Else if you don't find matches for the hash
Create a temp list
Append the current list to our temp list
Add the temp list to the dictionary as a new hash entry
You didn't find any duplicates, so return false
Если у вас есть достаточно сильный алгоритм хеширования для ваших входных данных, вам, возможно, даже не придется выполнять подсравнения, поскольку не будет конфликтов хэшей.
У меня есть пример кода. Недостающие биты:
Вот код:
public bool ContainsDuplicate(List<List<int>> input)
{
var encounteredLists = new Dictionary<int, List<EnumerableWrapper>>();
foreach (List<int> currentList in input)
{
var currentListWrapper = new EnumerableWrapper(currentList);
int hash = currentListWrapper.GetHashCode();
if (encounteredLists.ContainsKey(hash))
{
foreach (EnumerableWrapper currentEncounteredEntry in encounteredLists[hash])
{
if (currentListWrapper.Equals(currentEncounteredEntry))
return true;
}
}
else
{
var newEntry = new List<EnumerableWrapper>();
newEntry.Add(currentListWrapper);
encounteredLists[hash] = newEntry;
}
}
return false;
}
sealed class EnumerableWrapper
{
public EnumerableWrapper(IEnumerable<int> list)
{
if (list == null)
throw new ArgumentNullException("list");
this.List = list;
}
public IEnumerable<int> List { get; private set; }
public override bool Equals(object obj)
{
bool result = false;
var other = obj as EnumerableWrapper;
if (other != null)
result = Enumerable.SequenceEqual(this.List, other.List);
return result;
}
public override int GetHashCode()
{
// Todo: Implement your own hashing algorithm here
var sb = new StringBuilder();
foreach (int value in List)
sb.Append(value.ToString());
return sb.ToString().GetHashCode();
}
}
Вот потенциальная идея (предполагается, что значения являются числовыми):
Реализовать компаратор, который умножает каждый член каждой коллекции на его индекс, а затем суммирует все это:
Value: 0 5 8 3 2 0 5 3 5 1
Index: 1 2 3 4 5 6 7 8 9 10
Multiple: 0 10 24 12 10 0 35 24 45 10
Контрольная сумма члена: 170
Итак, вся «строка» имеет номер, который меняется в зависимости от членов и порядка. Быстро вычислять и сравнивать.
Что-то вроде этого даст вам правильные результаты:
List<List<int>> list = new List<List<int>>(){
new List<int>() {0 ,1 ,2, 3, 4, 5, 6 },
new List<int>() {0 ,1 ,2, 3, 4, 5, 6 },
new List<int>() {0 ,1 ,4, 2, 4, 5, 6 },
new List<int>() {0 ,3 ,2, 5, 1, 6, 4 }
};
list.ToLookup(l => String.Join(",", l.Select(i => i.ToString()).ToArray()))
.Where(lk => lk.Count() > 1)
.SelectMany(group => group);
Если вы не занимаетесь серьезно тяжелой работой, возможно, вам подойдет следующий простой код:
var lists = new List<List<int>>()
{
new List<int>() {0 ,1, 2, 3, 4, 5, 6 },
new List<int>() {0 ,1, 2, 3, 4, 5, 6 },
new List<int>() {0 ,1, 4, 2, 4, 5, 6 },
new List<int>() {0 ,3, 2, 5, 1, 6, 4 }
};
var duplicates = from list in lists
where lists.Except(new[] { list }).Any(l => l.SequenceEqual(list))
select list;
Очевидно, что вы могли бы повысить производительность, если бы вручную настроили алгоритм так, чтобы вам не приходилось просматривать списки. каждой итерации, но есть что сказать о написании декларативного, более простого кода.
(Кроме того, благодаря удивительности LINQ®, добавлению вызова .AsParallel() к приведенному выше коду, алгоритм будет работать на нескольких ядрах, таким образом, работая потенциально быстрее, чем сложные, настраиваемые вручную решения, упомянутые в этом нить.)
Как насчет того, чтобы написать свой собственный компаратор списков:
class ListComparer:IEqualityComparer<List<int>>
{
public bool Equals(List<int> x, List<int> y)
{
if(x.Count != y.Count)
return false;
for(int i = 0; i < x.Count; i++)
if(x[i] != y[i])
return false;
return true;
}
public int GetHashCode(List<int> obj)
{
return base.GetHashCode();
}
}
а затем просто:
var nonDuplicatedList = list.Distinct(new ListComparer());
var distinctCount = nonDuplicatedList.Count();
Есть здесь уже есть ряд хороших решений, но я считаю, что это будет работать быстрее всех, если только не будет какой-то структуры данных, о которой вы нам еще не рассказали.
List>
List
вычислите хэш, используя некоторый простой функция вида (...((x0)*a + x1)*a + ...)*a + xN)
, которую можно вычислить рекурсивно; a
должно быть чем-то вроде 1367130559 (т. е. некоторым большим простым числом, случайно не близким ни к какой интересующей степени 2. List
к накапливающемуся списку. Если нет, возьмите List
, который вы искали на первой карте, и List
, которые вы тестировали, и добавьте новую запись во вторую карту, содержащую список этих двух элементов.List>
и отсортируйте его лексикографически. Теперь просто выполните сравнения на равенство, чтобы подсчитать количество разных блоков. Если у вас есть N неповторяющихся элементов и M элементов, которые дублируются из набора из K элементов, вам потребуется O(N+M+2K) для создания исходных хэш-карт, в самом худшем случае O (M log M), чтобы выполнить сортировку (и, вероятно, больше похоже на O (M log (M/K))) и O (M), чтобы выполнить окончательный тест на равенство.