Bir IEnumerable'ın başka bir IEnumerable'ın tüm öğelerini içerip içermediğini kontrol edin


103

Her iki koleksiyondaki her öğenin bir alanını / özelliğini karşılaştırırken bir IEnumerable'ın başka bir IEnumerable'ın tüm öğelerini içerip içermediğini belirlemenin en hızlı yolu nedir?


public class Item
{
    public string Value;

    public Item(string value)
    {
        Value = value;
    }
}

//example usage

Item[] List1 = {new Item("1"),new Item("a")};
Item[] List2 = {new Item("a"),new Item("b"),new Item("c"),new Item("1")};

bool Contains(IEnumerable<Item> list1, IEnumerable<Item>, list2)
{
    var list1Values = list1.Select(item => item.Value);
    var list2Values = list2.Select(item => item.Value);

    return //are ALL of list1Values in list2Values?
}

Contains(List1,List2) // should return true
Contains(List2,List1) // should return false

1
Listeleriniz hangi yönde? Liste1'deki tüm öğelerin liste 2'de mi yoksa liste2'deki tüm öğelerin liste 1'de mi olduğunu kontrol etmek ister misiniz?
Mark Byers

Yanıtlar:


140

Bir koleksiyondaki tüm değerlerin başka bir koleksiyonda yer alıp almadığını belirleyen bir durumu izlemediğiniz ve sürdürmediğiniz sürece bunu yapmanın "hızlı yolu" yoktur. Sadece IEnumerable<T>karşı çalışmak zorunda kalırsan , kullanırdım Intersect.

var allOfList1IsInList2 = list1.Intersect(list2).Count() == list1.Count();

Bunun performansı çok makul olmalıdır, çünkü Intersect()her liste sadece bir kez sıralanacaktır. Ayrıca, Count()temel tür ICollection<T>yalnızca bir IEnumerable<T>.


Bazı testler yaptım ve bu yöntem diğerlerinden daha hızlı çalışıyor gibi görünüyor. Bahşiş için teşekkürler.
Brandon Zacharie

2
Listede kopyalar varsa bu çalışmaz. Örneğin, 441 ve 414'ün bir char dizisini karşılaştırmak 41'i döndürür ve dolayısıyla sayım başarısız olur.
John

69

İkinci listedeki tüm değerleri ilk listeden kaldırmak için Hariç'i de kullanabilir ve ardından tüm değerlerin kaldırılıp kaldırılmadığını kontrol edebilirsiniz:

var allOfList1IsInList2 = !list1.Except(list2).Any();

Bu yöntemin iki Count () çağrısı gerektirmeme avantajı vardı.


Bu aynı zamanda Liste1'de olanı ama Liste2'de olmayanları bulmak için de iyidir;
Homer

16
Bu, list1'in yinelenen değerlere sahip olduğu durumlarda çalışır. Kabul edilen cevap değil.
dbc

23

C # 3.5+

Enumerable.All<TSource>Tüm List2 öğelerinin Liste1'de yer alıp almadığını belirlemek için kullanılır :

bool hasAll = list2Uris.All(itm2 => list1Uris.Contains(itm2));

Bu, list1 list2'nin tüm öğelerinden daha fazlasını içerdiğinde de çalışacaktır.


10
Ah, bir Contains()arama içindeki bir All()aramanın performans etkileri konusunda .
Kent Boogaart

Ayrıca bunu grup yöntemine de taşıyabilirsiniz: bool hasAll = list2Uris.All (list1Uris.Contains);
jimpanzer

IEnumerable <T> türlerinde bu çözüm n * m performansı sağlayacaktır.
Dmitriy Dokshin

5
Steno: bool hasAll = list2Uris.All(list1Uris.Contains);
Illuminator

3

Kent'in cevabı güzel ve kısa, ancak sağladığı çözüm her zaman ilk koleksiyonun tamamında yinelemeyi gerektiriyor. İşte kaynak kodu:

public static IEnumerable<TSource> Intersect<TSource>(this IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    if (first == null)
        throw Error.ArgumentNull("first");
    if (second == null)
        throw Error.ArgumentNull("second");
    return Enumerable.IntersectIterator<TSource>(first, second, comparer);
}

private static IEnumerable<TSource> IntersectIterator<TSource>(IEnumerable<TSource> first, IEnumerable<TSource> second, IEqualityComparer<TSource> comparer)
{
    Set<TSource> set = new Set<TSource>(comparer);
    foreach (TSource source in second)
        set.Add(source);
    foreach (TSource source in first)
    {
        if (set.Remove(source))
            yield return source;
    }
}

Bu her zaman gerekli değildir. İşte benim çözümüm:

public static bool Contains<T>(this IEnumerable<T> source, IEnumerable<T> subset, IEqualityComparer<T> comparer)
{
    var hashSet = new HashSet<T>(subset, comparer);
    if (hashSet.Count == 0)
    {
        return true;
    }

    foreach (var item in source)
    {
        hashSet.Remove(item);
        if (hashSet.Count == 0)
        {
            break;
        }
    }

    return hashSet.Count == 0;
}

Aslında, ISet<T>( HashSet<T>) kullanmayı düşünmelisiniz . Gerekli tüm set yöntemlerini içerir. IsSubsetOfSenin durumunda.


2

Linq operatörü SequenceEqual de çalışır (ancak numaralandırılabilir öğelerin aynı sırada olmasına duyarlıdır)

return list1Uris.SequenceEqual(list2Uris);

2

Cevap olarak işaretlenen çözüm, tekrarlar durumunda başarısız olacaktır. IEnumerable'ınız yalnızca farklı değerler içeriyorsa, o zaman geçer.

Aşağıdaki cevap, tekrarlı 2 liste içindir:

        int aCount = a.Distinct().Count();
        int bCount = b.Distinct().Count();

        return aCount == bCount &&
               a.Intersect(b).Count() == aCount;

Bu, tüm kopyaları kaldırdığı ve gerçekte karşılaştırmadığı için iyi bir çözüm değildir.
John

2

Array yerine HashSet kullanmalısınız.

Misal:

List1.SetEquals(List2); //returns true if the collections contains exactly same elements no matter the order they appear in the collection

Referans

Tek HasSet sınırlaması, List gibi indekse göre öğe alamamak veya Sözlükler gibi Anahtar tarafından öğe alamamaktır. Tek yapabileceğiniz onları numaralandırmaktır (her biri için, süre vb.)

Lütfen bunun sizin için uygun olup olmadığını bana bildirin


-2

iki listeyi karşılaştırmak için bu yöntemi kullanabilirsiniz

    //Method to compare two list
    private bool Contains(IEnumerable<Item> list1, IEnumerable<Item> list2)
    {
        bool result;

        //Get the value
        var list1WithValue = list1.Select(s => s.Value).ToList();
        var list2WithValue = list2.Select(s => s.Value).ToList();

        result = !list1WithValue.Except(list2WithValue).Any();

        return result;
    }

Hemen hemen aynı yanıt 3 yıl önce verildi: stackoverflow.com/a/16967827/5282087
Dragomok
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.