我有一个列表列表,我想找到这样的交集:
var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };
// expected intersection is List<int>() { 3 };
有没有办法用IEnumerable.Intersect()做到这一点?
编辑:
我应该对此更清楚:我确实有一个列表列表,我不知道会有多少列表,上面的三个列表只是一个例子,我所拥有的实际上是IEnumerable<IEnumerable<SomeClass>>
感谢所有好的答案。事实证明有四个选项可以解决这个问题:列表+聚合(@Marcel Gosselin), List + foreach (@JaredPar,@ Gabe Moothart), HashSet +聚合(@ jesperll)和 HashSet + foreach (@Tony the Pony)。我对这些解决方案进行了一些性能测试(每个列表中列表数,元素数,随机数最大大小。
事实证明,在大多数情况下,HashSet的性能优于List(除了大型列表和小的随机数大小,因为我猜HashSet的性质。) 我找不到foreach方法和聚合方法之间的任何真正区别(foreach方法执行稍微更好。)
对我来说,聚合方法真的很吸引人(我将其视为已接受的答案),但我不会说这是最可读的解决方案..再次感谢所有人!
答案 0 :(得分:59)
怎么样:
var intersection = listOfLists
.Skip(1)
.Aggregate(
new HashSet<T>(listOfLists.First()),
(h, e) => { h.IntersectWith(e); return h; }
);
通过使用相同的HashSet并仍然在单个语句中进行优化。只需确保listOfLists始终包含至少一个列表。
答案 1 :(得分:55)
您确实可以使用Intersect
两次。但是,我相信这会更有效率:
HashSet<int> hashSet = new HashSet<int>(list1);
hashSet.IntersectWith(list2);
hashSet.IntersectWith(list3);
List<int> intersection = hashSet.ToList();
当然不是小套装的问题,但如果你有很多大套装,它可能很重要。
基本上Enumerable.Intersect
需要在每次调用时创建一个集合 - 如果你知道你将要做更多的集合操作,那么你也可以保持这个集合。
与往常一样,密切关注性能与可读性 - 两次调用Intersect
的方法链接非常有吸引力。
编辑:对于更新的问题:
public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
HashSet<T> hashSet = null;
foreach (var list in lists)
{
if (hashSet == null)
{
hashSet = new HashSet<T>(list);
}
else
{
hashSet.IntersectWith(list);
}
}
return hashSet == null ? new List<T>() : hashSet.ToList();
}
或者如果你知道它不会是空的,那么Skip会相对便宜:
public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
HashSet<T> hashSet = new HashSet<T>(lists.First());
foreach (var list in lists.Skip(1))
{
hashSet.IntersectWith(list);
}
return hashSet.ToList();
}
答案 2 :(得分:24)
试试这个,它有效,但我真的想摆脱聚合中的.ToList()。
var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };
var intersection = listOfLists.Aggregate((previousList, nextList) => previousList.Intersect(nextList).ToList());
<强>更新强>
根据@pomber的评论,可以摆脱ToList()
调用中的Aggregate
并将其移到外面仅执行一次。我没有测试性能是否比以前的代码更快。所需的更改是在最后一行指定Aggregate
方法的泛型类型参数,如下所示:
var intersection = listOfLists.Aggregate<IEnumerable<int>>(
(previousList, nextList) => previousList.Intersect(nextList)
).ToList();
答案 3 :(得分:4)
您可以执行以下操作
var result = list1.Intersect(list2).Intersect(list3).ToList();
答案 4 :(得分:4)
这是我的解决方案版本,其中包含一个名为IntersectMany的扩展方法。
public static IEnumerable<TResult> IntersectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
using (var enumerator = source.GetEnumerator())
{
if(!enumerator.MoveNext())
return new TResult[0];
var ret = selector(enumerator.Current);
while (enumerator.MoveNext())
{
ret = ret.Intersect(selector(enumerator.Current));
}
return ret;
}
}
所以用法就是这样的:
var intersection = (new[] { list1, list2, list3 }).IntersectMany(l => l).ToList();
答案 5 :(得分:1)
这是我没有交叉函数的列表列表(ListOfLists)的单行解决方案:
var intersect = ListOfLists.SelectMany(x=>x).Distinct().Where(w=> ListOfLists.TrueForAll(t=>t.Contains(w))).ToList()
这适用于.net 4(或更高版本)
答案 6 :(得分:0)
在搜索了网络并且没有真正想出我喜欢的东西(或者说有效)之后,我就睡了一觉并想出了这个。我使用了一个类SearchResult
),其中包含EmployeeId
,这是我需要在列表中共同使用的东西。我返回每个列表中都有EmployeeId
的所有记录。它并不华丽,但它简单易懂,只是我喜欢的东西。对于小名单(我的情况),它应该表现得很好 - 任何人都可以理解它!
private List<SearchResult> GetFinalSearchResults(IEnumerable<IEnumerable<SearchResult>> lists)
{
Dictionary<int, SearchResult> oldList = new Dictionary<int, SearchResult>();
Dictionary<int, SearchResult> newList = new Dictionary<int, SearchResult>();
oldList = lists.First().ToDictionary(x => x.EmployeeId, x => x);
foreach (List<SearchResult> list in lists.Skip(1))
{
foreach (SearchResult emp in list)
{
if (oldList.Keys.Contains(emp.EmployeeId))
{
newList.Add(emp.EmployeeId, emp);
}
}
oldList = new Dictionary<int, SearchResult>(newList);
newList.Clear();
}
return oldList.Values.ToList();
}
这里有一个例子,只是使用一个int列表,而不是一个类(这是我原来的实现)。
static List<int> FindCommon(List<List<int>> items)
{
Dictionary<int, int> oldList = new Dictionary<int, int>();
Dictionary<int, int> newList = new Dictionary<int, int>();
oldList = items[0].ToDictionary(x => x, x => x);
foreach (List<int> list in items.Skip(1))
{
foreach (int i in list)
{
if (oldList.Keys.Contains(i))
{
newList.Add(i, i);
}
}
oldList = new Dictionary<int, int>(newList);
newList.Clear();
}
return oldList.Values.ToList();
}
答案 7 :(得分:-1)
如果您的列表都很小,这是一个简单的解决方案。如果您有更大的列表,那么它的表现就不如哈希集:
public static IEnumerable<T> IntersectMany<T>(this IEnumerable<IEnumerable<T>> input)
{
if (!input.Any())
return new List<T>();
return input.Aggregate(Enumerable.Intersect);
}