Controllare se un array è un sottoinsieme di un altro


145

Qualche idea su come verificare se tale elenco è un sottoinsieme di un altro?

In particolare, ho

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

Come verificare che t2 sia un sottoinsieme di t1, usando LINQ?


Se le liste sono ordinate (come nel tuo esempio), ciò dovrebbe essere possibile in O (n + m) tempo.
Colonnello Panic,

Risposte:


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


@Bul Ikana Il funzionamento di questo codice è semplice, il metodo di estensione chiama internamente Equals e GetHashCode dei metodi della classe di oggetti sovrascritti se non è stato fornito IEqualityComparer per il lavoro.
Mrinal Kamboj,

2
Se le liste sono lunghe n e m, qual è la complessità temporale di questo algoritmo?
Colonnello Panic,

2
Sarebbe bello se questo fosse ridotto a un metodo linq chiamato ContainsAll
Sebastian Patten,

60

Utilizzare HashSet invece di Elenco se si lavora con i set. Quindi puoi semplicemente usare IsSubsetOf ()

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

bool isSubset = t2.IsSubsetOf(t1);

Mi dispiace che non usi LINQ. :-(

Se è necessario utilizzare gli elenchi, la soluzione di @ Jared funziona con l'avvertenza che sarà necessario rimuovere eventuali elementi ripetuti esistenti.


3
Esattamente. Vuoi un'operazione impostata, usa la classe progettata per loro. La soluzione di Cameron è creativa, ma non chiara / espressiva come HashSet.
tecnophile il

2
Ehm, non sono d'accordo perché la domanda dice specificamente "usa LINQ".
JaredPar,

9
@JaredPar: E allora? Non è meglio mostrare a qualcuno la strada giusta rispetto a come vogliono andare?
Jonathan Allen,

Una lista mantiene il suo ordine ma una serie no. Se l'ordine è importante, ciò darebbe risultati errati.
UuDdLrLrSs,

11

Se si esegue il test unitario è anche possibile utilizzare il metodo CollectionAssert.IsSubsetOf :

CollectionAssert.IsSubsetOf(subset, superset);

Nel caso precedente ciò significherebbe:

CollectionAssert.IsSubsetOf(t2, t1);

7

Questa è una soluzione significativamente più efficiente rispetto alle altre pubblicate qui, in particolare la soluzione migliore:

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

Se riesci a trovare un singolo elemento in t2 che non è in t1, allora sai che t2 non è un sottoinsieme di t1. Il vantaggio di questo metodo è che viene eseguito tutto sul posto, senza allocare spazio aggiuntivo, a differenza delle soluzioni che utilizzano .Except o .Intersect. Inoltre, questa soluzione è in grado di rompersi non appena trova un singolo elemento che viola la condizione del sottoinsieme, mentre gli altri continuano a cercare. Di seguito è la forma lunga ottimale della soluzione, che è solo leggermente più veloce nei miei test rispetto alla soluzione abbreviata sopra.

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

Ho fatto un'analisi rudimentale delle prestazioni di tutte le soluzioni e i risultati sono drastici. Queste due soluzioni sono circa 100 volte più veloci delle soluzioni .Except () e .Intersect () e non utilizzano memoria aggiuntiva.


Questo è esattamente ciò che !t2.Except(t1).Any()sta facendo. Linq sta lavorando avanti e indietro. Any()chiede IEnumerablese esiste almeno un elemento. In questo scenario t2.Except(t1)sta emettendo solo il primo elemento di t2cui non è presente t1. Se il primo elemento di t2non è in t1esso termina più velocemente, se tutti gli elementi di t2sono in t1esso viene eseguito il più lungo.
circa

Durante la riproduzione in giro con una sorta di punto di riferimento, ho scoperto, quando si prende t1={1,2,3,...9999}e t2={9999,9998,99997...9000}, si ottengono i seguenti misure: !t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms. E peggiora quanto più grande è la gamma.
circa

2
Non è così che funziona Linq. t2.Except (t1)sta restituendo un IEnumerablenon a Collection. Emette tutti gli elementi possibili solo se si scorre completamente su di esso, ad esempio da ToArray ()o ToList ()o si utilizza foreachsenza rompere all'interno. Cerca l' esecuzione differita di linq per saperne di più su questo concetto.
circa

1
Sono pienamente consapevole di come funziona l'esecuzione differita in Linq. Puoi rimandare l'esecuzione tutto ciò che desideri, ma quando vuoi determinare se t2 è un sottoinsieme di t1, dovrai iterare l'intero elenco per capirlo. Non si può aggirare questo fatto.
user2325458,

2
Prendiamo l'esempio dal tuo commento t2={1,2,3,4,5,6,7,8} t1={2,4,6,8} t2.Except(t1)=> il primo elemento di t2 = 1 => la differenza da 1 a t1 è 1 (verificato su {2,4,6,8}) => Except()emette il primo elemento 1 => Any()ottiene un elemento => Any()risulta true => nessun ulteriore controllo degli elementi in t2.
circa

6

@ La soluzione di Cameron come metodo di estensione:

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

Uso:

bool isSubset = t2.IsSubsetOf(t1);

(Questo è simile, ma non è lo stesso di quello pubblicato sul blog di @ Michael)


0

Basandomi sulle risposte di @Cameron e @Neil, ho scritto un metodo di estensione che utilizza la stessa terminologia della classe 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();
}

0

Qui controlliamo che se ci sono elementi presenti nella lista figlio (cioè t2) che non è contenuta nella lista padre (cioè t1). Se nessuno di questi esiste allora l'elenco è sottoinsieme dell'altro

per esempio:

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

-1

Prova questo

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

L'idea qui è che Intersect restituirà solo i valori presenti in entrambe le matrici. A questo punto se la lunghezza dell'insieme risultante è uguale all'insieme originale, anche tutti gli elementi in "set" sono in "check" e quindi "set" è un sottoinsieme di "toCheck"

Nota: la mia soluzione non funziona se "set" ha duplicati. Non lo sto cambiando perché non voglio rubare i voti degli altri.

Suggerimento: ho votato a favore della risposta di Cameron.


4
Funziona se sono davvero dei set, ma non se il secondo "set" contiene elementi ripetuti poiché è davvero un elenco. È possibile che si desideri utilizzare HashSet <double> per assicurarsi che abbia impostato la semantica.
tvanfosson,

non funziona quando entrambi gli array hanno elementi, che non si trovano nell'altro array.
da_berni,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.