Verifique se uma matriz é um subconjunto de outra


145

Alguma idéia de como verificar se essa lista é um subconjunto de outra?

Especificamente, eu tenho

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

Como verificar se t2 é um subconjunto de t1, usando LINQ?


Se as listas são classificadas (como no seu exemplo), isso deve ser possível em O (n + m).
Coronel Panic

Respostas:


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


@Bul Ikana O trabalho com este código é simples, o método de extensão chama internamente os Equals e GetHashCode dos métodos da classe de objeto substituídos, se não houver IEqualityComparer fornecido para o trabalho.
Mrinal Kamboj

2
Se as listas tiverem comprimento n e m, qual é a complexidade de tempo desse algoritmo?
Coronel Panic

2
Seria bom se isso foi resumido a um método linq chamado ContainsAll
Sebastian Patten

60

Use HashSet em vez de List se estiver trabalhando com conjuntos. Então você pode simplesmente usar IsSubsetOf ()

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

bool isSubset = t2.IsSubsetOf(t1);

Desculpe por não usar o LINQ. :-(

Se você precisar usar listas, a solução do @ Jared funcionará com a ressalva de que você precisará remover todos os elementos repetidos existentes.


3
Exatamente. Você deseja uma operação definida, use a classe projetada para eles. A solução de Cameron é criativa, mas não tão clara / expressiva quanto o HashSet.
Technophile

2
Hum, eu discordo, porque a pergunta diz especificamente "use LINQ".
JaredPar 2/08/08

9
@JaredPar: E daí? Não é melhor mostrar a alguém o caminho certo do que o jeito que eles querem seguir?
Jonathan Allen

Uma lista mantém sua ordem, mas um conjunto não. Se o pedido for importante, isso daria resultados incorretos.
UUDdLrLrSs

11

Se você estiver testando a unidade, também poderá utilizar o método CollectionAssert.IsSubsetOf :

CollectionAssert.IsSubsetOf(subset, superset);

No caso acima, isso significaria:

CollectionAssert.IsSubsetOf(t2, t1);

7

Esta é uma solução significativamente mais eficiente que as outras postadas aqui, especialmente a solução principal:

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

Se você puder encontrar um único elemento em t2 que não esteja em t1, saberá que t2 não é um subconjunto de t1. A vantagem desse método é que ele é feito no local, sem alocar espaço adicional, ao contrário das soluções usando .Except ou .Intersect. Além disso, esta solução pode quebrar assim que encontrar um único elemento que viole a condição do subconjunto, enquanto os outros continuam pesquisando. Abaixo está a forma longa ideal da solução, que é marginalmente mais rápida em meus testes do que a solução abreviada acima.

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

Fiz algumas análises rudimentares de desempenho de todas as soluções e os resultados são drásticos. Essas duas soluções são cerca de 100x mais rápidas que as soluções .Except () e .Intersect () e não usam memória adicional.


Isso é exatamente o que !t2.Except(t1).Any()está fazendo. O Linq está trabalhando de um lado para o outro. Any()está perguntando IEnumerablese existe pelo menos um elemento. Nesse cenário, t2.Except(t1)está emitindo apenas o primeiro elemento do t2qual não está t1. Se o primeiro elemento de t2não estiver t1nele, ele termina mais rápido, se todos os elementos de lá t2estiverem, são t1executados por mais tempo.
ABTO

Enquanto a brincar com algum tipo de referência, eu descobri, quando você toma t1={1,2,3,...9999}e t2={9999,9998,99997...9000}, você tem as seguintes medidas: !t2.Except(t1).Any(): 1ms -> t2.All(e => t1.Contains(e)): 702ms. E fica pior quanto maior o alcance.
ABTO

2
Não é assim que o Linq funciona. t2.Except (t1)está retornando um IEnumerablenão a Collection. Ele só emite todos os itens possíveis se você iterar completamente sobre ele, por exemplo, por ToArray ()ou ToList ()ou usar foreachsem quebrar o interior. Pesquise a execução adiada do linq para ler mais sobre esse conceito.
Abr 3/14/14

1
Estou totalmente ciente de como a execução adiada funciona no Linq. Você pode adiar a execução o quanto quiser, mas quando quiser determinar se t2 é um subconjunto de t1, precisará iterar a lista inteira para descobrir isso. Não há como contornar esse fato.
precisa saber é o seguinte

2
Vamos dar o exemplo do seu comentário t2={1,2,3,4,5,6,7,8} t1={2,4,6,8} t2.Except(t1)=> primeiro elemento de t2 = 1 => a diferença de 1 para t1 é 1 (marcada com {2,4,6,8}) => Except()emite o primeiro elemento 1 => Any()obtém um elemento => Any()resulta em true => nenhuma verificação adicional dos elementos em t2.
ABTO

6

@ A solução da Cameron como método de extensão:

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

Uso:

bool isSubset = t2.IsSubsetOf(t1);

(Isso é semelhante, mas não o mesmo que o postado no blog do @ Michael)


0

Com base nas respostas de @Cameron e @Neil, escrevi um método de extensão que usa a mesma terminologia que a 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

Aqui, verificamos que, se houver algum elemento presente na lista filho (por exemplo t2) que não esteja contido na lista pai (por exemplo t1). Se nenhum deles existir, a lista será um subconjunto do outro

por exemplo:

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

-1

Tente isto

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

A idéia aqui é que o Intersect retornará apenas os valores que estão nas duas matrizes. Nesse momento, se o comprimento do conjunto resultante for igual ao conjunto original, todos os elementos em "conjunto" também estarão em "verificação" e, portanto, "conjunto" será um subconjunto de "toCheck"

Nota: Minha solução não funcionará se "set" tiver duplicatas. Não estou mudando, porque não quero roubar os votos de outras pessoas.

Dica: votei na resposta de Cameron.


4
Isso funciona se eles são de fato conjuntos, mas não se o segundo "conjunto" contém elementos repetidos, pois é realmente uma lista. Você pode usar o HashSet <double> para garantir que ele tenha definido a semântica.
tvanfosson 02/12/08

não funciona quando ambas as matrizes têm elementos que não estão na outra matriz.
Da_berni 28/05
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.