我的工作有问题,希望减少到以下内容:我有两个List<int>
,我想查看int
中的ListA
是否有任何问题等于int
中的任何ListB
。 (它们可以是阵列,如果这样可以让生活更轻松,但我认为List<>
有一些可能有帮助的内置魔法。)我确信这是一个LINQ友好的问题,但我在2.0工作这里。
到目前为止,我的解决方案是foreach
通过ListA,然后是foreach
通过ListB,
foreach (int a in ListA)
{
foreach (int b in ListB)
{
if (a == b)
{
return true;
}
}
}
当它们每个长三个项目时实际上非常光滑,但是现在它们长200并且它们经常不匹配,所以我们得到N ^ 2比较的最坏情况。甚至40,000次比较也相当快,但我想我可能会遗漏一些东西,因为N ^ 2对于这个特殊问题似乎很天真。
谢谢!
答案 0 :(得分:38)
使用LINQ,这是微不足道的,因为您可以调用Intersect
extension method上的Enumerable
class来为您提供两个数组的集合交集:
var intersection = ListA.Intersect(ListB);
但是,这是 set 交集,意味着如果ListA
和ListB
中没有唯一值,则不会获得任何副本。换句话说,如果您有以下内容:
var ListA = new [] { 0, 0, 1, 2, 3 };
var ListB = new [] { 0, 0, 0, 2 };
然后ListA.Intersect(ListB)
产生:
{ 0, 2 }
如果你期待:
{ 0, 0, 2 }
然后,当您扫描两个列表时,您将不得不自己维护项目的数量并产生/减少。
首先,您需要收集Dictionary<TKey, int>
个别项目列表:
var countsOfA = ListA.GroupBy(i => i).ToDictionary(g => g.Key, g => g.Count());
从那里,当您遇到ListB
中的项目时,您可以扫描countsOfA
并将其放在列表中:
// The items that match.
IList<int> matched = new List<int>();
// Scan
foreach (int b in ListB)
{
// The count.
int count;
// If the item is found in a.
if (countsOfA.TryGetValue(b, out count))
{
// This is positive.
Debug.Assert(count > 0);
// Add the item to the list.
matched.Add(b);
// Decrement the count. If
// 0, remove.
if (--count == 0) countsOfA.Remove(b);
}
}
你可以将它包装在一个推迟执行的扩展方法中:
public static IEnumerable<T> MultisetIntersect(this IEnumerable<T> first,
IEnumerable<T> second)
{
// Call the overload with the default comparer.
return first.MultisetIntersect(second, EqualityComparer<T>.Default);
}
public static IEnumerable<T> MultisetIntersect(this IEnumerable<T> first,
IEnumerable<T> second, IEqualityComparer<T> comparer)
{
// Validate parameters. Do this separately so check
// is performed immediately, and not when execution
// takes place.
if (first == null) throw new ArgumentNullException("first");
if (second == null) throw new ArgumentNullException("second");
if (comparer == null) throw new ArgumentNullException("comparer");
// Defer execution on the internal
// instance.
return first.MultisetIntersectImplementation(second, comparer);
}
private static IEnumerable<T> MultisetIntersectImplementation(
this IEnumerable<T> first, IEnumerable<T> second,
IEqualityComparer<T> comparer)
{
// Validate parameters.
Debug.Assert(first != null);
Debug.Assert(second != null);
Debug.Assert(comparer != null);
// Get the dictionary of the first.
IDictionary<T, long> counts = first.GroupBy(t => t, comparer).
ToDictionary(g => g.Key, g.LongCount(), comparer);
// Scan
foreach (T t in second)
{
// The count.
long count;
// If the item is found in a.
if (counts.TryGetValue(t, out count))
{
// This is positive.
Debug.Assert(count > 0);
// Yield the item.
yield return t;
// Decrement the count. If
// 0, remove.
if (--count == 0) counts.Remove(t);
}
}
}
请注意,这两种方法都是(如果我在这里屠杀Big-O表示法,我会道歉)O(N + M)
其中N
是第一个数组中的项目数,{{1是第二个数组中的项数。您必须只扫描每个列表一次,并且假设获取哈希码并对哈希码执行查找是M
(常量)操作。
答案 1 :(得分:7)
将整个ListA加载到HashSet实例中,然后针对HastSet测试ListB中的foreach项:我很确定这将是O(N)。
//untested code ahead
HashSet<int> hashSet = new HashSet<int>(ListA);
foreach (int i in ListB)
{
if (hashSet.Contains(i))
return true;
}
以下是一行中的相同内容:
return new HashSet<int>(ListA).Overlaps(ListB);
.NET 3.5中不存在HashSet,因此在.NET 2.0中,您可以使用Dictionary<int,object>
(而不是使用HashSet<int>
),并始终将null存储为字典中的对象/值你只对钥匙感兴趣。
答案 2 :(得分:3)
不要遍历每个列表,而是查看List.Contains方法:
foreach (int a in ListA)
{
if (ListB.Contains(a))
return true;
}
答案 3 :(得分:2)
Chris通过散列提供O(N)解决方案。现在,根据常数因素(由于散列),可能值得考虑通过排序的O(N log(N))解决方案。根据您的使用情况,您可以考虑使用几种不同的变体。
对ListB进行排序(O(N log(N)),并使用搜索算法解析ListA中的每个元素(同样是O(N)* O(log(N)))。
对ListA和ListB(O(N log(N))进行排序,并使用O(N)算法比较这些重复项列表。
如果两个列表将被多次使用,则首选第二种方法。
答案 4 :(得分:0)
如何使用BinarySearch方法而不是迭代内部循环中的所有元素?