De "o 2 está em A?" para "todo A cabe em B?": subconjunto, subconjunto próprio, igualdade e a distinção ∈ vs ⊆, com Set na prática.
Cada conceito da matemática aqui vem com o mesmo conceito em JavaScript, lado a lado. Até aqui toda pergunta era sobre um elemento ("o 2 está em A?"). Subconjunto muda o nível: agora a gente compara dois conjuntos inteiros. É a matemática por trás de "todo X é Y", e é isso que decide se um usuário entra, se um plano libera uma feature e se seus testes cobrem os casos.
Este post cobre: subconjunto (
⊆), subconjunto próprio (⊂), igualdade de conjuntos e a distinção∈vs⊆. Apoia-se nos fundamentos (pertinência, cardinalidade, vazio) do material anterior.
1. Subconjunto (⊆): a virada de chave
A é subconjunto de B quando todo elemento de A também está em B:
A ⊆ B quando, para todo x ∈ A, vale x ∈ B.
Lê-se "A está contido em B". A imagem é de encaixe: A cabe inteiro dentro de B, sem sobrar nada de fora.
A = {2, 4}, B = {1, 2, 3, 4, 5}, então A ⊆ B é verdadeiro.
O 2 está em B, o 4 está em B, nenhum elemento de A ficou de fora.
O ponto central: subconjunto não é uma checagem nova. É a pertinência (∈) aplicada a todos os elementos de uma vez. Você já tinha a peça (has); subconjunto só a embrulha num "para todo". Em código, isso é every (o "para todo" do JS) por cima de has (o ∈):
const A = new Set([2, 4]);
const B = new Set([1, 2, 3, 4, 5]);
// A ⊆ B ? -> "para TODO x em A, x ∈ B ?"
const contido = [...A].every(x => B.has(x));
console.log(contido); // true
A matemática e o código são a mesma frase:
| Matemática | Código |
|---|---|
A ⊆ B |
[...A].every(x => B.has(x)) |
"para todo x em A…" |
[...A].every(x => ...) |
"…x ∈ B" |
B.has(x) |
Um contra-exemplo já basta. Se um só elemento de
Afalhar, já não é subconjunto, por mais que todos os outros passem. É exatamente o comportamento doevery, que retornafalseno primeiro que falha. Isso reaparece a carreira inteira: para afirmar um "para todo" você checa todos; para refutá-lo, basta achar um que quebra.
2. Os dois casos que pegam todo mundo
Saem direto da definição, mas parecem estranhos na primeira vez.
Todo conjunto é subconjunto de si mesmo: A ⊆ A, sempre. Todo elemento de A está em A, trivialmente.
O vazio é subconjunto de qualquer conjunto: ∅ ⊆ B, para todo B. Aplicando a definição ao pé da letra: "todo elemento de ∅ está em B?". Como ∅ não tem elemento nenhum, não existe ninguém pra violar a regra, então vale. Isso se chama verdadeiro por vacuidade, e é o mesmo "para todo sobre conjunto vazio é sempre verdadeiro" da lógica.
const A = new Set([1, 2]);
const vazio = new Set();
[...A].every(x => A.has(x)); // true -> A ⊆ A
[...vazio].every(x => A.has(x)); // true -> ∅ ⊆ A (every de vazio é sempre true)
3. Subconjunto próprio (⊂)
O subconjunto próprio é a versão mais exigente: A cabe em B e B tem pelo menos um elemento a mais.
A ⊂ B quando A ⊆ B e A ≠ B.
O atalho pra nunca errar é a analogia com os números:
| Conjuntos | Números | Permite igual? |
|---|---|---|
A ⊆ B (contido) |
a ≤ b |
sim |
A ⊂ B (próprio) |
a < b |
não |
⊆ é o "≤"; ⊂ é o "<". Assim como 3 < 3 é falso, B ⊂ B é falso, um conjunto nunca é subconjunto próprio de si mesmo.
Com B = {1,2,3}: {1,2} ⊂ B é verdadeiro; {1,2,3} ⊂ B é falso; {1,2,3} ⊆ B é verdadeiro.
O caso do meio é o pulo do gato: {1,2,3} é igual a B, então cabe (⊆ vale), mas não é próprio (⊂ é falso), porque não sobra nada. Em código:
const A = new Set([1, 2]);
const B = new Set([1, 2, 3]);
const contido = [...A].every(x => B.has(x)); // A ⊆ B ?
const proprio = contido && A.size !== B.size; // A ⊂ B ?
console.log(contido, proprio); // true true
Cuidado com a notação: alguns autores usam
⊂no sentido geral (permitindo igual, como o nosso⊆). É uma inconsistência histórica. Sem ambiguidade:⊆= contido (permite igual) e⊊= contido próprio (estritamente menor). Se vir⊂por aí, confira o que o autor quis dizer.
4. Igualdade de conjuntos
Dois conjuntos são iguais quando têm exatamente os mesmos elementos. A forma precisa usa subconjunto nos dois sentidos:
A = B se e somente se A ⊆ B e B ⊆ A.
Cada um cabe dentro do outro. Se todo elemento de A está em B e todo elemento de B está em A, não podem diferir em nada. É assim que se prova que dois conjuntos são iguais: mostra as duas continências.
5. ∈ vs ⊆: a distinção que mais confunde
Este é o clássico do tópico. Os dois "falam de estar dentro", mas ligam tipos diferentes:
∈liga um elemento a um conjunto:2 ∈ A.⊆liga um conjunto a outro conjunto:{2} ⊆ A.
A diferença entre 2 e {2} é a diferença entre o número e a caixa contendo o número:
| Afirmação | Tipos | Veredito |
|---|---|---|
2 ∈ A |
elemento ∈ conjunto | V |
2 ⊆ A |
elemento ⊆ conjunto (mal formado) | F |
{2} ⊆ A |
conjunto ⊆ conjunto | V |
2 ∈ A (o número está lá) e {2} ⊆ A (a caixa cabe lá) são ambos verdadeiros, mas por relações diferentes. 2 ⊆ A usa a relação errada pro tipo errado.
A regra que resolve de vez: olhe o símbolo primeiro.
∈espera elemento à esquerda;⊆espera conjunto à esquerda. O símbolo diz que tipo cada lado precisa ser.
6. Por que isso importa no seu dia a dia como dev
Sempre que você precisa garantir que um grupo inteiro está dentro de outro grupo, é ⊆ trabalhando, mesmo sem ninguém escrever o símbolo.
Autorização (o exemplo nº 1). "Esse usuário pode entrar?" é uma pergunta de subconjunto: os papéis exigidos estão contidos nos papéis que o usuário tem?
const exigidos = new Set(["editor"]);
const doUsuario = new Set(["editor", "revisor", "leitor"]);
// exigidos ⊆ doUsuario ? -> "tem TODOS os papéis exigidos?"
const podeEntrar = [...exigidos].every(p => doUsuario.has(p));
console.log(podeEntrar); // true
Planos e feature flags. Grátis ⊂ Pro ⊂ Enterprise, cada plano superior contém as features do de baixo e acrescenta mais. É continência encadeada.
Validação de schema. "Os campos recebidos são um subconjunto dos permitidos?" Se vier campo fora do conjunto permitido, rejeita.
const permitidos = new Set(["nome", "email", "idade"]);
const recebidos = Object.keys(payload);
const valido = recebidos.every(campo => permitidos.has(campo)); // recebidos ⊆ permitidos ?
Tipos e herança. Quando a linguagem aceita Cachorro onde se espera Animal, é porque trata Cachorro como subtipo, subconjunto aplicado a tipos. "Todo Cachorro é um Animal." É a base do polimorfismo.
Cobertura de teste. Se o conjunto testado é subconjunto próprio do conjunto de casos reais (testado ⊂ possível), sobra caso não coberto, e é aí que o bug se esconde. O ⊂ carrega justamente a informação "está contido mas ainda falta coisa".