IEnumerable.Intersect () के साथ कई सूचियों का अंतर्ग्रहण


85

मेरे पास सूचियों की एक सूची है जो मैं इस तरह के लिए चौराहा खोजना चाहता हूं:

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), सूची + foreach (@JaredPar, @Gabe Moothart), HashSet + समुच्चय (@jjperll) और HashSet + foreach (@Tony the Pony)। मैंने इन समाधानों (कुछ सूचियों की संख्या , प्रत्येक सूची में तत्वों की संख्या और यादृच्छिक संख्या अधिकतम आकार) पर कुछ प्रदर्शन परीक्षण किया ।

यह पता चला है कि अधिकांश स्थितियों के लिए हैशसेट सूची से बेहतर प्रदर्शन करता है (बड़ी सूचियों और छोटे यादृच्छिक संख्या आकार को छोड़कर, हशसेट की प्रकृति के कारण मुझे लगता है।) मुझे फॉर्च विधि और कुल के बीच कोई वास्तविक अंतर नहीं मिला। विधि (foreach विधि थोड़ा बेहतर प्रदर्शन करती है।)

मेरे लिए, समग्र विधि वास्तव में आकर्षक है (और मैं स्वीकृत उत्तर के रूप में उस के साथ जा रहा हूं) लेकिन मैं यह नहीं कहूंगा कि यह सबसे पठनीय समाधान है .. फिर से धन्यवाद!

जवाबों:


74

कैसा रहेगा:

var intersection = listOfLists
    .Skip(1)
    .Aggregate(
        new HashSet<T>(listOfLists.First()),
        (h, e) => { h.IntersectWith(e); return h; }
    );

इस तरह यह एक ही हैशसेट का उपयोग करके और अभी भी एक ही बयान में अनुकूलित किया गया है। बस यह सुनिश्चित करें कि listOfLists में हमेशा कम से कम एक सूची हो।


1
वाह, कोई रास्ता नहीं है कि मैं इस समाधान के बारे में खुद सोच सकता था। एक बार जब आपके पास समाधान होता है, तो यह स्पष्ट लगता है ..... हम्मम, नहीं, मैं सिर्फ एक टिप्पणी छोड़ दूंगा यह सुनिश्चित करने के लिए कि मेरे सहकर्मी यह नहीं सोचेंगे कि मैं बहुत अधिक घास लेता हूं :)
शमूएल

कार्यात्मक प्रतिमान जीतता है)
एनाटोल

स्किप की आवश्यकता क्यों है? यह पूछने पर क्योंकि मुझे नहीं पता
Issa Fram

छोड़ना वहाँ है क्योंकि पहले तत्व का उपयोग हैशसेट की प्रारंभिक आबादी के लिए किया जाता है। आपको ऐसा करना चाहिए, क्योंकि अन्यथा यह एक खाली सेट के साथ चौराहों का एक गुच्छा है।
SirPentor

मैं उपाय समझता हूं। मुझे लगता है कि ई गणनाकर्ता के लिए खड़ा है? क्या मैं यह भी पूछ सकता हूं कि h का मतलब क्या है? मुझे लगता है कि H का मतलब HashSet है?
क्वान

63

आप वास्तव में 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();
}

या यदि आप जानते हैं कि यह खाली नहीं होगा, और यह स्किप अपेक्षाकृत सस्ता होगा:

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();
}

हाँ, foreach समझ में आता है। मार्सेल के जवाब में एग्रीगेट विधि की तुलना में इसके साथ कोई भी प्रदर्शन अंतर?
Oskar

@ ऑस्कर: हां, मेरा जवाब हर बार एक नया बनाने के बजाय एक ही हैशसेट का उपयोग करता है। हालाँकि, आप अभी भी एक सेट के साथ एग्रीगेट का उपयोग कर सकते हैं ... संपादित करेंगे।
जॉन स्कीट

Ick ... बस एक अलग समाधान बाहर काम करने की कोशिश की, और यह icky है क्योंकि HashSet.IntersectWull के साथ अशक्त देता है :(
जॉन स्कीट

1
नमस्ते। आपकी IntersectAll()विधि के बारे में एक प्रश्न (जो मुट्ठी भर है): मानों (जैसे:) की तुलना करने Func<TResult, TKey> selectorऔर अभी भी उपयोग करने के लिए एक चयनकर्ता को पैरामीटर के रूप में जोड़ने का एक सरल तरीका है InsertectWith()?
tigrou

@tigrou: बहुत आसानी से नहीं - क्योंकि आप अभी भी एक के List<T>बजाय List<TKey>, सही लौटना चाहते हैं ? संभवत: सबसे अच्छा तरीका यह बनाना होगा EqualityComparer<T>जिसे लागू करके लागू किया गया था TKey
जॉन स्कीट

29

यह कोशिश करो, यह काम करता है लेकिन मैं वास्तव में .TLList () से छुटकारा पाना चाहता हूं।

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());

अपडेट करें:

@ पॉम्बर की टिप्पणी के बाद, कॉल के ToList()अंदर से छुटकारा पाना Aggregateऔर इसे केवल एक बार निष्पादित करने के लिए इसे बाहर ले जाना संभव है । मैंने प्रदर्शन के लिए परीक्षण नहीं किया कि क्या पिछला कोड नए की तुलना में तेज है या नहीं। Aggregateनीचे की तरह अंतिम पंक्ति पर विधि के सामान्य प्रकार के पैरामीटर को निर्दिष्ट करने के लिए आवश्यक परिवर्तन है :

var intersection = listOfLists.Aggregate<IEnumerable<int>>(
   (previousList, nextList) => previousList.Intersect(nextList)
   ).ToList();

धन्यवाद, मैंने अभी-अभी कोशिश की है और यह काम करता है! पहले इस्तेमाल नहीं किया था () लेकिन मुझे लगता है कि यह कुछ ऐसा था जिसकी मुझे तलाश थी।
Oskar

जैसा कि मैंने टोनी के जवाब पर एक टिप्पणी के रूप में निर्दिष्ट किया है, मुझे विश्वास है कि उनका समाधान बेहतर प्रदर्शन करेगा।
मार्सेल गोसलिन

3
अगर आप Aggregate <IEnumerable <int >>
pomber

@pomber, मुझे विश्वास नहीं हो सकता कि आपकी टिप्पणी बिना अपवोट के 3 साल चली गई है। खैर आज आपका दिन है मेरे दोस्त।
शॉन

5

आप निम्नलिखित कर सकते हैं

var result = list1.Intersect(list2).Intersect(list3).ToList();

1
धन्यवाद, लेकिन मेरे पास वास्तव में सूचियों की एक सूची है, तीन अलग-अलग सूचियों की नहीं .. मुझे कुछ ऐसी चीज़ों की आवश्यकता है जो स्वतंत्र हों कि सूची में कितनी सूचियाँ हैं।
Oskar

4
@ ऑस्कर आप आसानी से चला सकते हैं कि लूप में
गेब मुथरत

5

यह एक विस्तार विधि के साथ समाधान का मेरा संस्करण है जिसे मैंने 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();

2

प्रतिच्छेदन फ़ंक्शन के बिना सूची (ListOfLists) के लिए यह मेरा एक-पंक्ति समाधान है:

var intersect = ListOfLists.SelectMany(x=>x).Distinct().Where(w=> ListOfLists.TrueForAll(t=>t.Contains(w))).ToList()

यह .net 4 (या बाद में) के लिए काम करना चाहिए


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();
}

यहां एक उदाहरण है कि केवल एक किलों की सूची का उपयोग करना, न कि एक वर्ग (यह मेरा मूल कार्यान्वयन था)।

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();
}

-1

यह एक सरल उपाय है यदि आपकी सूचियाँ सभी छोटी हैं। यदि आपके पास बड़ी सूची है, तो यह हैश सेट के रूप में प्रदर्शन नहीं कर रहा है:

public static IEnumerable<T> IntersectMany<T>(this IEnumerable<IEnumerable<T>> input)
{
    if (!input.Any())
        return new List<T>();

    return input.Aggregate(Enumerable.Intersect);
}
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.