Periksa apakah array adalah himpunan bagian dari yang lain

145

Adakah ide tentang cara memeriksa apakah daftar itu adalah subset dari yang lain?

Secara khusus, saya punya

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

Bagaimana memeriksa bahwa t2 adalah himpunan bagian dari t1, menggunakan LINQ?

Graviton
sumber
Jika daftar diurutkan (seperti dalam contoh Anda), ini harus dimungkinkan dalam waktu O (n + m).
Kolonel Panic

Jawaban:

255
bool isSubset = !t2.Except(t1).Any();
Cameron MacFarland
sumber
1
Saya telah membuat metode ekstensi geekswithblogs.net/mnf/archive/2011/05/13/…
Michael Freidgeim
@Bul Ikana Bekerja dengan kode ini sederhana, metode ekstensi secara internal memanggil Equals dan GetHashCode dari metode kelas objek yang ditimpa jika tidak ada IEqualityComparer yang disediakan untuk pekerjaan itu.
Mrinal Kamboj
2
Jika daftar panjangnya n dan m, berapa kompleksitas waktu dari algoritma ini?
Kolonel Panic
2
Akan lebih baik jika ini direbus ke metode LINQ yang disebut ContainsAll
Sebastian Patten
60

Gunakan HashSet sebagai ganti List jika bekerja dengan set. Maka Anda cukup menggunakan IsSubsetOf ()

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

bool isSubset = t2.IsSubsetOf(t1);

Maaf itu tidak menggunakan LINQ. :-(

Jika Anda perlu menggunakan daftar, maka solusi @ Jared bekerja dengan peringatan bahwa Anda perlu menghapus elemen berulang yang ada.

tvanfosson
sumber
3
Persis. Anda ingin operasi yang ditetapkan, gunakan kelas yang dirancang untuk mereka. Solusi Cameron adalah kreatif, tetapi tidak sejelas / ekspresif seperti HashSet.
technophile
2
Um Saya tidak setuju karena pertanyaannya secara spesifik mengatakan "gunakan LINQ".
JaredPar
9
@JaredPar: Jadi apa? Apakah tidak lebih baik untuk menunjukkan kepada seseorang cara yang benar daripada cara yang mereka inginkan?
Jonathan Allen
Daftar mempertahankan pesanannya tetapi satu set tidak. Jika pesanan penting ini akan memberikan hasil yang salah.
UuDdLrLrSs
11

Jika Anda menguji unit, Anda juga dapat menggunakan metode CollectionAssert.IsSubsetOf :

CollectionAssert.IsSubsetOf(subset, superset);

Dalam kasus di atas ini berarti:

CollectionAssert.IsSubsetOf(t2, t1);
Gza
sumber
7

Ini adalah solusi yang jauh lebih efisien daripada yang diposkan di sini, terutama solusi teratas:

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

Jika Anda dapat menemukan elemen tunggal di t2 yang tidak ada di t1, maka Anda tahu bahwa t2 bukan subset dari t1. Keuntungan dari metode ini adalah bahwa hal itu dilakukan semua di tempat, tanpa mengalokasikan ruang tambahan, tidak seperti solusi yang menggunakan .Except atau .Intersect. Lebih jauh lagi, solusi ini dapat pecah segera setelah menemukan satu elemen yang melanggar kondisi subset, sementara yang lain terus mencari. Di bawah ini adalah bentuk panjang optimal dari solusi, yang hanya sedikit lebih cepat dalam pengujian saya daripada solusi singkat di atas.

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

Saya melakukan beberapa analisis kinerja yang belum sempurna dari semua solusi, dan hasilnya sangat drastis. Kedua solusi ini sekitar 100x lebih cepat daripada .Except () dan .Intersect (), dan tidak menggunakan memori tambahan.

pengguna2325458
sumber
Itulah tepatnya yang !t2.Except(t1).Any()sedang dilakukan. Linq bekerja kembali ke sebagainya. Any()bertanya IEnumerableapakah ada setidaknya satu elemen. Dalam skenario t2.Except(t1)ini hanya memancarkan elemen pertama t2yang tidak ada dalam t1. Jika elemen pertama t2tidak ada di t1dalamnya selesai tercepat, jika semua elemen t2di t1dalamnya berjalan paling lama.
sekitar
Sambil bermain-main dengan semacam patokan, saya menemukan, ketika Anda mengambil t1={1,2,3,...9999}dan t2={9999,9998,99997...9000}, Anda mendapatkan pengukuran berikut: !t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms. Dan semakin buruk rentang semakin besar.
sekitar
2
Ini bukan cara kerja Linq. t2.Except (t1)mengembalikan IEnumerablebukan Collection. Ini hanya memancarkan semua item yang mungkin jika Anda beralih sepenuhnya di atasnya, misalnya dengan ToArray ()atau ToList ()atau digunakan foreachtanpa merusak di dalamnya. Cari eksekusi linq yang ditangguhkan untuk membaca lebih lanjut tentang konsep itu.
sekitar
1
Saya sepenuhnya menyadari bagaimana eksekusi yang ditangguhkan bekerja di Linq. Anda dapat menunda eksekusi yang Anda inginkan, tetapi ketika Anda ingin menentukan apakah t2 adalah himpunan bagian dari t1, Anda harus mengulangi seluruh daftar untuk mengetahuinya. Tidak ada jalan untuk menghindari kenyataan itu.
user2325458
2
Mari kita ambil contoh dari komentar Anda t2={1,2,3,4,5,6,7,8} t1={2,4,6,8} t2.Except(t1)=> elemen pertama t2 = 1 => perbedaan 1 ke t1 adalah 1 (diperiksa terhadap {2,4,6,8}) => Except()memancarkan elemen pertama 1 => Any()mendapat elemen => Any()menghasilkan true => tidak ada pemeriksaan elemen lebih lanjut di t2.
sekitar
6

@ Solusi Cameron sebagai metode ekstensi:

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

Pemakaian:

bool isSubset = t2.IsSubsetOf(t1);

(Ini mirip, tetapi tidak persis sama dengan yang diposting di blog @ Michael)

Neil
sumber
0

Membangun jawaban dari @Cameron dan @Neil saya menulis metode ekstensi yang menggunakan terminologi yang sama dengan kelas Enumerable.

/// <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();
}
sclarke81
sumber
0

Di sini kita memeriksa bahwa jika ada elemen yang ada dalam daftar anak (yaitu t2) yang tidak terkandung oleh daftar induk (yaitu t1). Jika tidak ada yang seperti itu maka daftar adalah subset dari yang lain

misalnya:

bool isSubset = !(t2.Any(x => !t1.Contains(x)));
Korek
sumber
-1

Coba ini

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

Idenya di sini adalah bahwa Intersect hanya akan mengembalikan nilai-nilai yang ada di kedua Array. Pada titik ini jika panjang himpunan yang dihasilkan sama dengan himpunan asli, maka semua elemen dalam "himpunan" juga dalam "centang" dan karenanya "himpunan" adalah himpunan bagian dari "toCheck"

Catatan: Solusi saya tidak berfungsi jika "set" memiliki duplikat. Saya tidak mengubahnya karena saya tidak ingin mencuri suara orang lain.

Petunjuk: Saya memilih jawaban Cameron.

JaredPar
sumber
4
Ini berfungsi jika mereka memang set, tetapi tidak jika "set" kedua berisi elemen berulang karena itu benar-benar daftar. Anda mungkin ingin menggunakan HashSet <double> untuk memastikan bahwa ia telah menetapkan semantik.
tvanfosson
tidak berfungsi ketika kedua Array memiliki elemen, yang tidak ada dalam Array lainnya.
da_berni