Bir dizinin başka bir dizinin alt kümesi olup olmadığını kontrol edin


149

Bu listenin başka bir listenin alt kümesi olup olmadığını nasıl kontrol edeceğiniz konusunda bir fikriniz var mı?

Özellikle ben var

List<double> t1 = new List<double> { 1, 3, 5 };
List<double> t2 = new List<double> { 1, 5 };

LINQ kullanarak t2'nin t1'in bir alt kümesi olup olmadığı nasıl kontrol edilir?


Listeler sıralanırsa (örneğinizdeki gibi), bu O (n + m) zamanında mümkün olmalıdır.
Albay Panic

Yanıtlar:


262
bool isSubset = !t2.Except(t1).Any();


@Bul Ikana Bu kodun çalışması basittir, uzantı yöntemi, iş için sağlanan IEqualityComparer yoksa, geçersiz kılınan nesne sınıfı yöntemlerinin Eşittir ve GetHash Kodunu dahili olarak çağırır.
Mrinal Kamboj

2
Listelerin uzunluğu n ve m ise, bu algoritmanın zaman karmaşıklığı nedir?
Albay Panic

2
Bu, ContainsAll adlı bir linq yöntemine indirgenmiş olsaydı iyi olurdu
Sebastian Patten

62

Kümelerle çalışıyorsanız Liste yerine HashSet kullanın. O zaman IsSubsetOf () 'u kullanabilirsiniz.

HashSet<double> t1 = new HashSet<double>{1,3,5};
HashSet<double> t2 = new HashSet<double>{1,5};

bool isSubset = t2.IsSubsetOf(t1);

LINQ kullanmadığı için üzgünüm. :-(

Listeleri kullanmanız gerekiyorsa, @ Jared'in çözümü, var olan yinelenen tüm öğeleri kaldırmanız gerekeceği uyarısıyla çalışır.


3
Kesinlikle. Belirli bir işlem istiyorsanız, onlar için tasarlanmış sınıfı kullanın. Cameron'un çözümü yaratıcıdır, ancak HashSet kadar açık / etkileyici değildir.
technophile

2
Katılmıyorum çünkü soru özellikle "LINQ kullan" diyor.
JaredPar

9
@JaredPar: Ne olmuş yani? Birine doğru yolu göstermek istediklerinden daha iyi değil mi?
Jonathan Allen

Bir liste sırasını korur, ancak bir küme bunu yapmaz. Sıra önemliyse bu yanlış sonuçlar verecektir.
UuDdLrLrSs

11

Eğer varsa birim test de yararlanabilirler CollectionAssert.IsSubsetOf yöntemi:

CollectionAssert.IsSubsetOf(subset, superset);

Yukarıdaki durumda bu şu anlama gelir:

CollectionAssert.IsSubsetOf(t2, t1);

10

Bu, burada yayınlanan diğerlerinden çok daha verimli bir çözüm, özellikle en iyi çözüm:

bool isSubset = t2.All(elem => t1.Contains(elem));

T2'de t1'de olmayan tek bir eleman bulabilirseniz, o zaman t2'nin t1'in bir alt kümesi olmadığını bilirsiniz. Bu yöntemin avantajı, .Except veya .Intersect kullanan çözümlerin aksine, ek alan tahsis etmeden tamamen yerinde yapılmasıdır. Dahası, bu çözüm alt küme koşulunu ihlal eden tek bir öğe bulur bulmaz bozulabilirken diğerleri aramaya devam eder. Aşağıda, testlerimde yukarıdaki kısa çözüme göre çok az daha hızlı olan çözümün en uygun uzun biçimi verilmiştir.

bool isSubset = true;
foreach (var element in t2) {
    if (!t1.Contains(element)) {
        isSubset = false;
        break;
    }
}

Tüm çözümlerin bazı temel performans analizlerini yaptım ve sonuçlar çok sert. Bu iki çözüm .Except () ve .Intersect () çözümlerinden yaklaşık 100 kat daha hızlıdır ve ek bellek kullanmaz.


İşte tam olarak ne !t2.Except(t1).Any()yapıyor. Linq ileri geri çalışıyor. Any()Bir soruyor IEnumerableen az bir eleman varsa. Bu senaryoda t2.Except(t1), sadece içinde t2olmayan ilk elemanı yayıyor t1. İlk öğesi ise t2değil t1tüm unsurları eğer, en hızlı bitirir t2içindedir t1en uzun çalışır.
2014

1
Kriter çeşit etrafında oynarken, ben alırken, öğrendim t1={1,2,3,...9999}ve t2={9999,9998,99997...9000}şu değerlendirmeler elde: !t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms. Ve menzil büyüdükçe kötüleşiyor.
2014

2
Linq'in çalışma şekli bu değil. t2.Except (t1)bir IEnumerablenot döndürüyor Collection. Örneğin, üzerinde tamamen tarafından yineleme eğer tek olası tüm öğeleri yayar ToArray ()veya ToList ()veya kullanım foreachiçini bozmadan. Bu kavram hakkında daha fazla bilgi edinmek için linq ertelenmiş yürütmeyi arayın .
2014

1
Linq'te ertelenmiş yürütmenin nasıl çalıştığının tamamen farkındayım. Yürütmeyi istediğiniz kadar erteleyebilirsiniz, ancak t2'nin t1'in bir alt kümesi olup olmadığını belirlemek istediğinizde, anlamak için tüm listeyi yinelemeniz gerekir. Bu gerçeği aşmak yok.
user2325458

2
Yorumunuzdaki örneği alalım t2={1,2,3,4,5,6,7,8} t1={2,4,6,8} t2.Except(t1)=> t2'nin ilk elemanı = 1 => 1'den t1'e fark 1'dir ({2,4,6,8} ile kontrol edilir) => Except()ilk elemanı verir 1 => Any()bir eleman alır => Any()sonuç true => t2'de elemanların daha fazla denetlenmesine gerek kalmaz.
2014

6

@ Cameron'ın bir genişletme yöntemi olarak çözümü:

public static bool IsSubsetOf<T>(this IEnumerable<T> a, IEnumerable<T> b)
{
    return !a.Except(b).Any();
}

Kullanım:

bool isSubset = t2.IsSubsetOf(t1);

(Bu benzerdir, ancak @ Michael'ın blogunda yayınlananla tamamen aynı değildir)


2

BenchmarkDotNet ile bu cevapta 3 çalışma çözümünü karşılaştırmak için biraz zaman harcadım.

  • !t2.Except(t1).Any() Except (). Herhangi biri ()
  • t2.IsSubsetOf(t1) HashSet adlı
  • t2.All(elem => t1.Contains(elem)) Tümü (=> İçerir) olarak adlandırılır

3 parametresi değiştirdim:

  • süperset sayısı
  • alt kümenin uzunluğu
  • öğelerdeki değişkenlik

Tüm üst kümeler rastgele uzunluktaydı ve aralıktaki öğeleri içeriyordu [0; Variability). Alt kümeler sabit uzunluktaydı ve aralıktaki öğeleri içeriyordu [0; Variability).

Kazanan, Tümü (=> İçerir) yöntemidir - bazen HashSets kullanmaktan 30 kat daha hızlı ve Except () 'den 50 kat daha hızlıdır. Any ().

Kod github'da bulunabilir

GÜNCELLEME @Gert Arnold tarafından yapılan yorumlarda belirtildiği gibi, karşılaştırmamda bir hata vardı. ToHashSet()Her yinelemenin içini aradım . Bu yüzden önceden oluşturulmuş bir hashset koleksiyonu oluşturdum ve adlı bir saf hashset testi ekledim prebuilt HashSet. Mutlak bir kazanan değildir ve bazen All(=>Contains)algoritmayı kaybeder . Bana öyle geliyor ki mesele, süper setlerdeki değişkenlik (ve kopyalar). Değerlerdeki değişkenlik düşük olduğunda - hashset, onu verilerden kaldırdığı için kazanır.

Final masası buraya gelecek:

|             Method | SupersetsInIteration | SubsetLength | Variability |          Mean |        Error |       StdDev |        Median |
|------------------- |--------------------- |------------- |------------ |--------------:|-------------:|-------------:|--------------:|
|     Except().Any() |                 1000 |           10 |         100 |   1,485.89 μs |     7.363 μs |     6.887 μs |   1,488.36 μs |
|        ToHashSet() |                 1000 |           10 |         100 |   1,015.59 μs |     5.099 μs |     4.770 μs |   1,015.43 μs |
| prebuilt HashSet   |                 1000 |           10 |         100 |      38.76 μs |     0.065 μs |     0.054 μs |      38.78 μs |
|    All(=>Contains) |                 1000 |           10 |         100 |     105.46 μs |     0.320 μs |     0.267 μs |     105.38 μs |
|     Except().Any() |                 1000 |           10 |       10000 |   1,912.17 μs |    38.180 μs |    87.725 μs |   1,890.72 μs |
|        ToHashSet() |                 1000 |           10 |       10000 |   1,038.70 μs |    20.028 μs |    40.459 μs |   1,019.35 μs |
| prebuilt HashSet   |                 1000 |           10 |       10000 |      28.22 μs |     0.165 μs |     0.155 μs |      28.24 μs |
|    All(=>Contains) |                 1000 |           10 |       10000 |      81.47 μs |     0.117 μs |     0.109 μs |      81.45 μs |
|     Except().Any() |                 1000 |           50 |         100 |   4,888.22 μs |    81.268 μs |    76.019 μs |   4,854.42 μs |
|        ToHashSet() |                 1000 |           50 |         100 |   4,323.23 μs |    21.424 μs |    18.992 μs |   4,315.16 μs |
| prebuilt HashSet   |                 1000 |           50 |         100 |     186.53 μs |     1.257 μs |     1.176 μs |     186.35 μs |
|    All(=>Contains) |                 1000 |           50 |         100 |   1,173.37 μs |     2.667 μs |     2.227 μs |   1,173.08 μs |
|     Except().Any() |                 1000 |           50 |       10000 |   7,148.22 μs |    20.545 μs |    19.218 μs |   7,138.22 μs |
|        ToHashSet() |                 1000 |           50 |       10000 |   4,576.69 μs |    20.955 μs |    17.499 μs |   4,574.34 μs |
| prebuilt HashSet   |                 1000 |           50 |       10000 |      33.87 μs |     0.160 μs |     0.142 μs |      33.85 μs |
|    All(=>Contains) |                 1000 |           50 |       10000 |     131.34 μs |     0.569 μs |     0.475 μs |     131.24 μs |
|     Except().Any() |                10000 |           10 |         100 |  14,798.42 μs |   120.423 μs |   112.643 μs |  14,775.43 μs |
|        ToHashSet() |                10000 |           10 |         100 |  10,263.52 μs |    64.082 μs |    59.942 μs |  10,265.58 μs |
| prebuilt HashSet   |                10000 |           10 |         100 |   1,241.19 μs |     4.248 μs |     3.973 μs |   1,241.75 μs |
|    All(=>Contains) |                10000 |           10 |         100 |   1,058.41 μs |     6.766 μs |     6.329 μs |   1,059.22 μs |
|     Except().Any() |                10000 |           10 |       10000 |  16,318.65 μs |    97.878 μs |    91.555 μs |  16,310.02 μs |
|        ToHashSet() |                10000 |           10 |       10000 |  10,393.23 μs |    68.236 μs |    63.828 μs |  10,386.27 μs |
| prebuilt HashSet   |                10000 |           10 |       10000 |   1,087.21 μs |     2.812 μs |     2.631 μs |   1,085.89 μs |
|    All(=>Contains) |                10000 |           10 |       10000 |     847.88 μs |     1.536 μs |     1.436 μs |     847.34 μs |
|     Except().Any() |                10000 |           50 |         100 |  48,257.76 μs |   232.573 μs |   181.578 μs |  48,236.31 μs |
|        ToHashSet() |                10000 |           50 |         100 |  43,938.46 μs |   994.200 μs | 2,687.877 μs |  42,877.97 μs |
| prebuilt HashSet   |                10000 |           50 |         100 |   4,634.98 μs |    16.757 μs |    15.675 μs |   4,643.17 μs |
|    All(=>Contains) |                10000 |           50 |         100 |  10,256.62 μs |    26.440 μs |    24.732 μs |  10,243.34 μs |
|     Except().Any() |                10000 |           50 |       10000 |  73,192.15 μs |   479.584 μs |   425.139 μs |  73,077.26 μs |
|        ToHashSet() |                10000 |           50 |       10000 |  45,880.72 μs |   141.497 μs |   125.433 μs |  45,860.50 μs |
| prebuilt HashSet   |                10000 |           50 |       10000 |   1,620.61 μs |     3.507 μs |     3.280 μs |   1,620.52 μs |
|    All(=>Contains) |                10000 |           50 |       10000 |   1,460.01 μs |     1.819 μs |     1.702 μs |   1,459.49 μs |
|     Except().Any() |               100000 |           10 |         100 | 149,047.91 μs | 1,696.388 μs | 1,586.803 μs | 149,063.20 μs |
|        ToHashSet() |               100000 |           10 |         100 | 100,657.74 μs |   150.890 μs |   117.805 μs | 100,654.39 μs |
| prebuilt HashSet   |               100000 |           10 |         100 |  12,753.33 μs |    17.257 μs |    15.298 μs |  12,749.85 μs |
|    All(=>Contains) |               100000 |           10 |         100 |  11,238.79 μs |    54.228 μs |    50.725 μs |  11,247.03 μs |
|     Except().Any() |               100000 |           10 |       10000 | 163,277.55 μs | 1,096.107 μs | 1,025.299 μs | 163,556.98 μs |
|        ToHashSet() |               100000 |           10 |       10000 |  99,927.78 μs |   403.811 μs |   337.201 μs |  99,812.12 μs |
| prebuilt HashSet   |               100000 |           10 |       10000 |  11,671.99 μs |     6.753 μs |     5.986 μs |  11,672.28 μs |
|    All(=>Contains) |               100000 |           10 |       10000 |   8,217.51 μs |    67.959 μs |    56.749 μs |   8,225.85 μs |
|     Except().Any() |               100000 |           50 |         100 | 493,925.76 μs | 2,169.048 μs | 1,922.805 μs | 493,386.70 μs |
|        ToHashSet() |               100000 |           50 |         100 | 432,214.15 μs | 1,261.673 μs | 1,180.169 μs | 431,624.50 μs |
| prebuilt HashSet   |               100000 |           50 |         100 |  49,593.29 μs |    75.300 μs |    66.751 μs |  49,598.45 μs |
|    All(=>Contains) |               100000 |           50 |         100 |  98,662.71 μs |   119.057 μs |   111.366 μs |  98,656.00 μs |
|     Except().Any() |               100000 |           50 |       10000 | 733,526.81 μs | 8,728.516 μs | 8,164.659 μs | 733,455.20 μs |
|        ToHashSet() |               100000 |           50 |       10000 | 460,166.27 μs | 7,227.011 μs | 6,760.150 μs | 457,359.70 μs |
| prebuilt HashSet   |               100000 |           50 |       10000 |  17,443.96 μs |    10.839 μs |     9.608 μs |  17,443.40 μs |
|    All(=>Contains) |               100000 |           50 |       10000 |  14,222.31 μs |    47.090 μs |    44.048 μs |  14,217.94 μs |


Karşılaştırma adil değil. ToHashSet()Ölçülen koda dahil edersiniz . Karma kümeler oluşturup yalnızca karşılaştırırsanız sonuçlar oldukça farklı olacaktır IsSubsetOf. Hiç şüphe yok ki HashSetkolayca kazanacak.
Gert Arnold

İyi nokta, teşekkürler! Global kurulumda hazırlanmış hashsetlerde testleri yeniden çalıştıracağım. Bu belirli veri yapısını genel iş kodunda genellikle kullanmadığımız için karşılaştırma koduna ekledim, ancak kontrol edeceğim.
zetroot

0

@Cameron ve @Neil'den gelen yanıtlara dayanarak, Enumerable sınıfıyla aynı terminolojiyi kullanan bir genişletme yöntemi yazdım.

/// <summary>
/// Determines whether a sequence contains the specified elements by using the default equality comparer.
/// </summary>
/// <typeparam name="TSource">The type of the elements of source.</typeparam>
/// <param name="source">A sequence in which to locate the values.</param>
/// <param name="values">The values to locate in the sequence.</param>
/// <returns>true if the source sequence contains elements that have the specified values; otherwise, false.</returns>
public static bool ContainsAll<TSource>(this IEnumerable<TSource> source, IEnumerable<TSource> values)
{
    return !values.Except(source).Any();
}

0

Biz 2 diziler var, o zaman göz önüne alındığında array1ve array2ve hepimiz denetlemek istediğiniz array1değerleri bulunmaktadır array2:

array1.All(ar1 => array2.Any(ar2 => ar2.Equals(ar1)));

Not: ar2.Equals(ar1)Eşitlik başka bir şekilde tanımlanırsa değiştirilebilir.


-1

Bunu dene

static bool IsSubSet<A>(A[] set, A[] toCheck) {
  return set.Length == (toCheck.Intersect(set)).Count();
}

Buradaki fikir, Kesişim'in yalnızca her iki Dizide bulunan değerleri döndürmesidir. Bu noktada, ortaya çıkan kümenin uzunluğu orijinal kümeyle aynıysa, "küme" içindeki tüm öğeler de "kontrol" altındadır ve bu nedenle "küme", "toCheck" in bir alt kümesidir.

Not: "set" yinelenenlere sahipse çözümüm çalışmaz. Değiştirmiyorum çünkü başkalarının oylarını çalmak istemiyorum.

İpucu: Cameron'un cevabına oy verdim.


4
Bu, gerçekten kümeler ise işe yarar, ancak ikinci "küme" gerçekten bir liste olduğu için tekrarlanan öğeler içeriyorsa işe yarar. Anlamsallık ayarladığından emin olmak için HashSet <çift> kullanmak isteyebilirsiniz.
tvanfosson

her iki Dizinin diğer Dizide olmayan öğeleri olduğunda çalışmaz.
da_berni

-1

Burada alt listede (yani t2) üst liste (yani t1) tarafından bulunmayan herhangi bir öğe olup olmadığını kontrol ederiz .

Örneğin:

bool isSubset = !(t2.Any(x => !t1.Contains(x)));

not Any notdüz ile aynıdır All.
jeromej
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.