IEnumerable.Intersect ()를 사용하여 여러 목록의 교차
다음과 같이 교차점을 찾고 싶은 목록이 있습니다.
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>>
해결책
모든 훌륭한 답변에 감사드립니다. 이 문제를 해결하기위한 네 가지 옵션이 있습니다 : List + aggregate (@Marcel Gosselin), List + foreach (@JaredPar, @Gabe Moothart), HashSet + aggregate (@jesperll) 및 HashSet + foreach (@Tony the Pony). 이 솔루션에 대한 성능 테스트를 수행했습니다 ( 목록 수 , 각 목록 의 요소 수 및 난수 최대 크기 변경).
대부분의 상황에서 HashSet은 List보다 성능이 더 좋습니다 (내가 추측하는 HashSet의 특성 때문에 큰 목록과 작은 난수 크기 제외). 방법 (foreach 방법이 약간 더 잘 수행 됩니다.)
나에게 집계 방법은 정말 매력적이지만 (그리고 나는 그것을 받아 들인 대답으로 갈 것입니다) 가장 읽기 쉬운 해결책이라고 말하지 않을 것입니다 .. 다시 한번 감사드립니다!
어때 :
var intersection = listOfLists
.Skip(1)
.Aggregate(
new HashSet<T>(listOfLists.First()),
(h, e) => { h.IntersectWith(e); return h; }
);
그렇게하면 전체적으로 동일한 HashSet을 사용하여 단일 문에서 최적화됩니다. listOfLists에 항상 하나 이상의 목록이 포함되어 있는지 확인하십시오.
실제로 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();
}
이것을 시도해보십시오, 작동하지만 집계에서 .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();
다음을 수행 할 수 있습니다.
var result = list1.Intersect(list2).Intersect(list3).ToList();
이것은 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();
이것은 교차 기능이없는 List of List (ListOfLists)에 대한 한 행 솔루션입니다.
var intersect = ListOfLists.SelectMany(x=>x).Distinct().Where(w=> ListOfLists.TrueForAll(t=>t.Contains(w))).ToList()
.net 4 이상에서 작동합니다.
After searching the 'net and not really coming up with something I liked (or that worked), I slept on it and came up with this. Mine uses a class (SearchResult
) which has an EmployeeId
in it and that's the thing I need to be common across lists. I return all records that have an EmployeeId
in every list. It's not fancy, but it's simple and easy to understand, just what I like. For small lists (my case) it should perform just fine—and anyone can understand it!
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();
}
Here's an example just using a list of ints, not a class (this was my original implementation).
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();
}
This is a simple solution if your lists are all small. If you have larger lists, it's not as performing as hash set:
public static IEnumerable<T> IntersectMany<T>(this IEnumerable<IEnumerable<T>> input)
{
if (!input.Any())
return new List<T>();
return input.Aggregate(Enumerable.Intersect);
}
'Programing' 카테고리의 다른 글
AngularJs에 숨겨진 가시성? (0) | 2020.10.19 |
---|---|
SQL Server에서 십진 열의 정밀도를 어떻게 변경합니까? (0) | 2020.10.19 |
천 단위 구분 기호로 쉼표가있는 경우 문자열을 숫자로 변환하는 방법은 무엇입니까? (0) | 2020.10.19 |
mailto : (to) 부분이없는 링크를 어떻게 만드나요? (0) | 2020.10.19 |
rspec에서 루비 디버그를 실행 중입니까? (0) | 2020.10.19 |