jQuery / Escolher pais pela quantidade de filhos

Filtre pais candidatos com .filter(function () { ... }) e compare $(this).children(".item").length em cada callback. === 2 significa exatamente dois e >= 2 pelo menos dois. Use find() em vez de children() apenas para todos os descendentes aninhados.

Definir o que cada pai conta

.filter() mantém membros da seleção que passam no teste. this é o pai DOM examinado; true o mantém. O resultado contém pais, não filhos, e não remove os demais do documento.

.children(".item") conta correspondências um nível abaixo. Sem seletor, títulos e outros filhos elementos também contam. Texto e comentários ficam fora nos dois casos.

.find(".item") inclui descendentes mais profundos. Beta tem um filho direto com dois dentro: três descendentes. Ocultos continuam no DOM e contam. Apenas visíveis exige uma regra adicional explícita.

Exemplo completo: exato ou mínimo

Salve tudo em UTF-8 como child-count-demo.html e abra no navegador. A Internet carrega o arquivo oficial do jQuery 4.0.0. Não exige servidor nem pacotes. Condições, reinício e adições foram executados no Edge para Windows com 3.7.1 e 4.0.0.

Contagens diretas: Alpha 2, Beta 1, Gamma 0 e Delta 3. D3 oculto conta; títulos e texto não são .item. Candidatos são apenas .group diretos de #board, sem objetos internos.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Filter parents by child count</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 20px; max-width: 760px; }
    label { display: block; margin: 8px 0; }
    select, input, button { font: inherit; max-width: 100%; }
    .group { border: 2px solid #aaa; padding: 12px; margin: 12px 0; }
    .group.chosen { border-color: teal; background: #e5f5f1; }
    .item { display: inline-block; padding: 4px; border: 1px solid #777; margin: 3px; }
    .item[hidden] { display: none; }
    .group h2 { font-size: 20px; margin: 0; }
    #result { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <label>Count scope <select id="scope"><option value="direct">Direct children</option><option value="descendant">All descendants</option></select></label>
  <label>Rule <select id="rule"><option value="exact">Exactly</option><option value="minimum">At least</option></select></label>
  <label>Required count <input id="count" type="number" min="0" step="1" value="2"></label>
  <button type="button" id="add">Add direct item to Alpha</button>
  <button type="button" id="reset">Reset</button>
  <div id="board">
    <section class="group" id="alpha"><h2>Alpha</h2><span class="item">A1</span><span class="item">A2</span> plain text <!-- comment --></section>
    <section class="group" id="beta"><h2>Beta</h2><div class="item">B1 <span class="item">B2</span><span class="item">B3</span></div></section>
    <section class="group" id="gamma"><h2>Gamma</h2>Text without an item</section>
    <section class="group" id="delta"><h2>Delta</h2><span class="item">D1</span><span class="item">D2</span><span class="item" hidden>D3 hidden</span></section>
  </div>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#board").html();
      let added = 0;
      function apply() {
        const $groups = $("#board > .group");
        const scope = $("#scope").val(), rule = $("#rule").val();
        const field = document.getElementById("count"), target = Number(field.value);
        $groups.removeClass("chosen");
        if (field.value === "" || !field.checkValidity() || !Number.isInteger(target) || target < 0) {
          $("#result").text("Enter a nonnegative integer."); return;
        }
        function count(group) {
          return scope === "direct" ? $(group).children(".item").length : $(group).find(".item").length;
        }
        const $selected = $groups.filter(function () {
          const n = count(this);
          return rule === "exact" ? n === target : n >= target;
        });
        $selected.addClass("chosen");
        const ids = $selected.map(function () { return this.id; }).get();
        const counts = $groups.map(function () { return this.id + "=" + count(this); }).get();
        $("#result").text(`scope=${scope}; rule=${rule}; count=${target}\nselected=${ids.join(",") || "(none)"}\n${counts.join("; ")}`);
      }
      $("#scope, #rule, #count").on("change input", apply);
      $("#add").on("click", function () {
        added++; $("<span>").addClass("item").text("Added " + added).appendTo("#alpha"); apply();
      });
      $("#reset").on("click", function () {
        $("#board").html(initial); added = 0;
        $("#scope").val("direct"); $("#rule").val("exact"); $("#count").val(2); apply();
      });
      apply();
    });
  </script>
</body>
</html>

Inicialmente só Alpha, com exatamente dois filhos, recebe destaque.

scope=direct; rule=exact; count=2
selected=alpha
alpha=2; beta=1; gamma=0; delta=3
  1. At least seleciona Alpha e Delta. Exatamente dois difere de pelo menos dois.
  2. All descendants acrescenta Beta: passa de um para três.
  3. Exactly 3 seleciona Beta e Delta por descendentes, mas só Delta por Direct children.
  4. Exactly 0 escolhe Gamma; At least 0 permite zero e escolhe os quatro.
  5. Após Reset, Add direct item to Alpha leva-a a três; exatamente dois resulta em (none). Nenhum pai é removido.
  6. Vazio, negativo ou fracionário mostra erro e remove destaques. Reset restaura estrutura e condições.

Total conjunto e teste por pai

Pressione Reset e execute nas ferramentas de desenvolvimento para comparar coleção conjunta com filtro individual:

const $groups = $("#board > .group");
console.log($groups.children(".item").length);
console.log($groups.filter(function () {
  return $(this).children(".item").length === 0;
}).map(function () { return this.id; }).get().join(","));

Seis é 2+1+0+3 combinado, sem informar qual pai tem dois. A segunda linha examina cada pai e retorna Gamma sem .item direto.

6
gamma

Uma função normal recebe this como pai atual. Trocar por seta mantendo this quebra a premissa. Com seta, receba explicitamente o segundo argumento elemento do callback.

Uma coleção filtrada salva não reavalia continuamente a condição. Após adicionar, o resultado antigo pode manter Alpha. O exemplo chama apply() com candidatos e números atuais. .filter() não observa alterações do DOM.

Escopo e erros comuns

Defina todos os elementos ou cartões, li ou controles específicos. Aqui só .item conta: Gamma é zero apesar de h2 e texto. Não significa contêiner totalmente vazio. Acrescente explicitamente visibilidade, disponibilidade ou estado dos dados quando necessário.

Se importa apenas existir um descendente, .has() pode ser mais simples. Quantidades exatas e mínimas ficam claras com números. Não confunda nth-child, posição entre irmãos, com quantidade total do pai.

Com milhares de pais e buscas profundas repetidas, limite candidatos e cálculos desnecessários. Mas não reutilize resultados antigos após mudanças estruturais. Confira estado inicial e atualizado.