Como contar certos elementos na matriz?

162

Eu tenho uma matriz:

[1, 2, 3, 5, 2, 8, 9, 2]

Gostaria de saber quantos 2s estão na matriz.

Qual é a maneira mais elegante de fazer isso em JavaScript sem fazer loop com forloop?

Leem
fonte

Respostas:

90

Muito simples:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}
Thor Jacobsen
fonte
53
Não, o que quero dizer é sem fazer loop com "for" #
Leem 25/05
9
@ Leem: Por que o loop está ruim? Sempre existe um loop em algum momento. Obviamente, você criaria uma função que oculta o loop. Essas solicitações "Não quero usar a ferramenta certa para o trabalho" nunca fizeram muito sentido para mim. E podemos discutir o que é mais elegante. Por exemplo, para mim, fazer uma chamada de função por elemento apenas para compará-lo a um valor não é elegante.
Felix Kling 25/05
2
para risos: alert (eval ( '(' + my_array.join ( '== 2) + (') + '== 2)')) jsfiddle.net/gaby_de_wilde/gujbmych
user40521
29
O OP provavelmente pensa que o loop é ruim, pois possui 5 linhas de código e requer um estado mutável. Um desenvolvedor que ler isso mais tarde terá que gastar algum tempo para verificar o que faz e perder o foco de sua tarefa. Uma abstração é muito superior: const count = countItems(array, 2);e os detalhes da implementação podem ser discutidos por dentro.
Joeytwiddle 14/07/19
2
Esta não é a resposta certa, porque a pergunta pede claramente não usar loops. Verifique minha solução que não faz uso de loops. stackoverflow.com/a/44743436/8211014
Luis Orantes
289

[ esta resposta é um pouco datada: leia as edições ]

Diga olá aos seus amigos: mape filtere reducee forEache everyetc.

(Ocasionalmente, apenas escrevo for-loops em javascript, devido à falta de escopo no nível do bloco; portanto, você precisa usar uma função como corpo do loop de qualquer maneira, se precisar capturar ou clonar seu índice ou valor de iteração. geralmente são mais eficientes, mas às vezes você precisa de um fechamento.)

A maneira mais legível:

[....].filter(x => x==2).length

(Poderíamos ter escrito .filter(function(x){return x==2}).length)

A seguir, é mais eficiente em termos de espaço (O (1) em vez de O (N)), mas não tenho certeza de quanto benefício / penalidade você pode pagar em termos de tempo (não mais que um fator constante desde a sua visita) cada elemento exatamente uma vez):

[....].reduce((total,x) => (x==2 ? total+1 : total), 0)

(Se você precisar otimizar esse trecho de código específico, um loop for pode ser mais rápido em alguns navegadores ... você pode testar as coisas em jsperf.com.)


Você pode ser elegante e transformá-lo em uma função de protótipo:

[1, 2, 3, 5, 2, 8, 9, 2].count(2)

Como isso:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(value) {
            return this.filter(x => x==value).length;
        }
    }
});

Você também pode usar a técnica antiga de loop for regular (consulte outras respostas) dentro da definição de propriedade acima (novamente, isso provavelmente seria muito mais rápido).


2017 editar :

Opa, essa resposta ficou mais popular que a resposta correta. Na verdade, basta usar a resposta aceita. Embora essa resposta possa ser engraçada, os compiladores js provavelmente não otimizam (ou não podem devido à especificação) desses casos. Então você realmente deve escrever um loop for simples:

Object.defineProperties(Array.prototype, {
    count: {
        value: function(query) {
            /* 
               Counts number of occurrences of query in array, an integer >= 0 
               Uses the javascript == notion of equality.
            */
            var count = 0;
            for(let i=0; i<this.length; i++)
                if (this[i]==query)
                    count++;
            return count;
        }
    }
});

Você pode definir uma versão .countStrictEq(...)que use a ===noção de igualdade. A noção de igualdade pode ser importante para o que você está fazendo! (por exemplo [1,10,3,'10'].count(10)==2, porque números como '4' == 4 em javascript ... portanto, chamá-lo .countEqou .countNonstrictenfatizar que ele usa o ==operador.)

Considere também usar sua própria estrutura de dados multiset (por exemplo, como a de python ' collections.Counter') para evitar ter que fazer a contagem em primeiro lugar.

class Multiset extends Map {
    constructor(...args) {
        super(...args);
    }
    add(elem) {
        if (!this.has(elem))
            this.set(elem, 1);
        else
            this.set(elem, this.get(elem)+1);
    }
    remove(elem) {
        var count = this.has(elem) ? this.get(elem) : 0;
        if (count>1) {
            this.set(elem, count-1);
        } else if (count==1) {
            this.delete(elem);
        } else if (count==0)
            throw `tried to remove element ${elem} of type ${typeof elem} from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)`;
            // alternatively do nothing {}
    }
}

Demo:

> counts = new Multiset([['a',1],['b',3]])
Map(2) {"a" => 1, "b" => 3}

> counts.add('c')
> counts
Map(3) {"a" => 1, "b" => 3, "c" => 1}

> counts.remove('a')
> counts
Map(2) {"b" => 3, "c" => 1}

> counts.remove('a')
Uncaught tried to remove element a of type string from Multiset, but does not exist in Multiset (count is 0 and cannot go negative)

Nota: No entanto, se você ainda deseja a maneira de programação funcional (ou uma linha descartável descartável sem substituir o Array.prototype), você pode escrevê-la com mais facilidade hoje em dia como [...].filter(x => x==2).length. Se você se preocupa com o desempenho, observe que, embora este seja assintoticamente o mesmo desempenho que o loop for (O (N) time), ele pode exigir O (N) memória extra (em vez da memória O (1)), porque quase certamente gere uma matriz intermediária e conte os elementos dessa matriz intermediária.

ninjagecko
fonte
1
Esta é uma boa solução FP, o único "problema" (irrelevante para a maioria dos casos) que cria uma matriz intermediária.
tokland 25/05
1
@tokland: Se isso for um problema, você pode fazerarray.reduce(function(total,x){return x==value? : total+1 : total}, 0)
ninjagecko
1
@ninjagecko Não deveria haver apenas um cólon no operador ternário? [...].reduce(function(total,x){return x==2 ? total+1 : total}, 0)
A.Krueger
2
@tokland Talvez o filtro não crie uma matriz intermediária. Um bom compilador de otimização pode reconhecer facilmente que apenas o comprimento da matriz é usado. Talvez nenhum dos compiladores JS atuais seja inteligente o suficiente para fazer isso, mas isso não é importante. Como Fowler diz: "Se algo dói, faça mais". É míope para evitar deficiências do compilador escrevendo código ruim. Se o compilador for péssimo, corrija-o. mlafeldt.github.io/blog/if-it-hurts-do-it-more-often
John Henckel
Eu consideraria isso uma solução ideal de 2017: const count = (list) => list.filter((x) => x == 2).length. Em seguida, use-o chamando count(list)where list é uma matriz de números. Você também pode fazer const count = (list) => list.filter((x) => x.someProp === 'crazyValue').lengthpara contar instâncias de crazyValue na matriz de objetos. Observe que é uma correspondência exata para a propriedade.
agm1984
71

Atualização do ES6 para JS:

Observe que você sempre deve usar iguais a triplos: ===para obter uma comparação correta:

// Let has local scope
let array = [1, 2, 3, 5, 2, 8, 9, 2]

// Functional filter with an Arrow function
array.filter(x => x === 2).length  // -> 3

A seguinte função Arrow unânime (função lambda) em JS:

(x) => {
   const k = 2
   return k * x
}

pode ser simplificado para este formulário conciso para uma única entrada:

x => 2 * x

onde o returnestá implícito.

Sverrisson
fonte
A função de filtro é mais eficiente do que usar o loop for es6?
Niklas
1
@ Niklas, acho que é o mesmo (já que ambos precisam verificar todos os elementos, O (N)), mas acho que depende do navegador e também do número de elementos e do computador quanto ao que se encaixa na memória cache. Então, acho que a resposta é: "É complexo" :)
Sverrisson
67

2017: se alguém ainda estiver interessado na pergunta, minha solução é a seguinte:

const arrayToCount = [1, 2, 3, 5, 2, 8, 9, 2];
const result = arrayToCount.filter(i => i === 2).length;
console.log('number of the found elements: ' + result);

Raild
fonte
8

Se você estiver usando lodash ou sublinhado, o método _.countBy fornecerá um objeto de totais agregados codificados por cada valor na matriz. Você pode transformar isso em uma linha só se precisar contar apenas um valor:

_.countBy(['foo', 'foo', 'bar'])['foo']; // 2

Isso também funciona bem em matrizes de números. O one-liner para o seu exemplo seria:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3
Coleman
fonte
5
Um enorme exagero. Como ele cria contadores para todos os elementos exclusivos. Tempo e armazenamento desperdiçados.
metalim
5

A maneira mais estranha de pensar em fazer isso é:

(a.length-(' '+a.join(' ')+' ').split(' '+n+' ').join(' ').match(/ /g).length)+1

Onde:

  • a é a matriz
  • n é o número a contar na matriz

Minha sugestão, use um tempo ou para loop ;-)

Gary Green
fonte
3

Não usar um laço normalmente significa entregar o processo ao longo de algum método que não usar um loop.

Aqui está uma maneira do nosso codificador odiador de loop satisfazer o seu ódio, a um preço:

var a=[1, 2, 3, 5, 2, 8, 9, 2];

alert(String(a).replace(/[^2]+/g,'').length);


/*  returned value: (Number)
3
*/

Você também pode chamar indexOf repetidamente, se estiver disponível como método de matriz, e mover o ponteiro da pesquisa a cada vez.

Isso não cria uma nova matriz, e o loop é mais rápido que um forEach ou filtro.

Pode fazer a diferença se você tiver um milhão de membros para olhar.

function countItems(arr, what){
    var count= 0, i;
    while((i= arr.indexOf(what, i))!= -1){
        ++count;
        ++i;
    }
    return count
}

countItems(a,2)

/*  returned value: (Number)
3
*/
Kennebec
fonte
2
Você pode reduzir seu regex para apenas String(a).match(/2/g).length + 1- embora tenha cuidado com isso ou sua implementação não funcionará bem com dois dígitos.
Gary Green
2
que tal [2, 22, 2]?
Oduvan
2

A maioria das soluções postadas usando funções de matriz, como filtro, estão incompletas porque não são parametrizadas.

Aqui está uma solução com a qual o elemento a ser contado pode ser definido em tempo de execução.

function elementsCount(elementToFind, total, number){
    return total += number==elementToFind;
}

var ar = [1, 2, 3, 5, 2, 8, 9, 2];
var elementToFind=2;
var result = ar.reduce(elementsCount.bind(this, elementToFind), 0);

A vantagem dessa abordagem é que poderia facilmente mudar a função para contar, por exemplo, o número de elementos maiores que X.

Você também pode declarar a função de redução embutida

var ar = [1, 2, 3, 5, 2, 8, 9, 2];
var elementToFind=2;
var result = ar.reduce(function (elementToFind, total, number){
    return total += number==elementToFind;
}.bind(this, elementToFind), 0);
Luis Orantes
fonte
var elementToFind=2; ... function (elementToFind, total, number){ return total += number==elementToFind; }.bind(this, elementToFind) ...é mais difícil de ler e não oferece vantagem sobre apenas ... (acc, x) => acc += number == 2.... Eu gosto do seu uso em +=vez de no acc + (number == 2)entanto. Parece uma sintaxe injustificada HACK.
Masterxilo # 15/18
2

Realmente, por que você precisaria mapou filterpara isso? reducenasceu "para este tipo de operações:

[1, 2, 3, 5, 2, 8, 9, 2].reduce( (count,2)=>count+(item==val), 0);

é isso aí! (se item==valem cada iteração, 1 será adicionado ao acumulador count, conforme trueserá resolvido em 1).

Como uma função:

function countInArray(arr, val) {
   return arr.reduce((count,item)=>count+(item==val),0)
}

Ou vá em frente e estenda suas matrizes:

Array.prototype.count = function(val) {
   return this.reduce((count,item)=>count+(item==val),0)
}
Yuval A.
fonte
2

É melhor envolvê-lo em função:

let countNumber = (array,specificNumber) => {
    return array.filter(n => n == specificNumber).length
}

countNumber([1,2,3,4,5],3) // returns 1
Justin Herrera
fonte
2

Aqui está uma maneira do ES2017 + de obter as contagens para todos os itens da matriz em O (N):

const arr = [1, 2, 3, 5, 2, 8, 9, 2];
const counts = {};

arr.forEach((el) => {
  counts[el] = counts[el] ? (counts[el] += 1) : 1;
});

Você também pode, opcionalmente, classificar a saída:

const countsSorted = Object.entries(counts).sort(([_, a], [__, b]) => a - b);

console.log (countsSorted) para sua matriz de exemplo:

[
  [ '2', 3 ],
  [ '1', 1 ],
  [ '3', 1 ],
  [ '5', 1 ],
  [ '8', 1 ],
  [ '9', 1 ]
]
Yanick J. Steinbeck
fonte
1

Eu acredito que o que você está procurando é uma abordagem funcional

    const arr = ['a', 'a', 'b', 'g', 'a', 'e'];
    const count = arr.filter(elem => elem === 'a').length;
    console.log(count); // Prints 3

elem === 'a' é a condição, substitua-a por sua.

Shantanu Bhadoria
fonte
Não vai imprimir 3, mas 0. Para corrigi-lo, a sua segunda linha deve ser count = arr.filter(elem => elem === 'a').lengthoucount = arr.filter(elem => {return elem === 'a'}).length
WPomier
1

Eu sou um fã iniciante da função de redução do array js.

const myArray =[1, 2, 3, 5, 2, 8, 9, 2];
const count = myArray.reduce((count, num) => num === 2 ? count + 1 : count, 0)

De fato, se você realmente deseja ter uma fantasia, pode criar uma função de contagem no protótipo Array. Então você pode reutilizá-lo.

Array.prototype.count = function(filterMethod) {
  return this.reduce((count, item) => filterMethod(item)? count + 1 : count, 0);
} 

Então faça

const myArray =[1, 2, 3, 5, 2, 8, 9, 2]
const count = myArray.count(x => x==2)
Scott Blanch
fonte
0

Solução por recursão

function count(arr, value) {
   if (arr.length === 1)    {
      return arr[0] === value ? 1 : 0;
   } else {
      return (arr.shift() === value ? 1 : 0) + count(arr, value);
   }
}

count([1,2,2,3,4,5,2], 2); // 3
Giffo
fonte
1
Isso lida com uma matriz vazia?
Andrew Grimm
@AndrewGrimm está certo. O caso base é arr.length == 0
Justin Meiners
Ótima solução! Eu estava tentando fazer algo usando a recursão apenas para praticar e seu exemplo era mais elegante do que o que eu estava fazendo. É definitivamente uma maneira mais complexa do que usar filter, reduceou uma simples forLoope também mais cara quando se olha para o desempenho, mas ainda é uma ótima maneira de fazê-lo com recursão. Minha única mudança é: acho que seria melhor criar uma função e adicionar um filtro dentro dela para copiar a matriz e evitar uma mutação da matriz original, depois use a recursiva como uma função interna.
R. Marques
0

var arrayCount = [1,2,3,2,5,6,2,8];
var co = 0;
function findElement(){
    arrayCount.find(function(value, index) {
      if(value == 2)
        co++;
    });
    console.log( 'found' + ' ' + co + ' element with value 2');
}

Eu faria algo assim:

var arrayCount = [1,2,3,4,5,6,7,8];

function countarr(){
  var dd = 0;
  arrayCount.forEach( function(s){
    dd++;
  });

  console.log(dd);
}

roni
fonte
0

Crie um novo método para a classe Array no arquivo de nível principal e use-o em todo o seu projeto.

// say in app.js
Array.prototype.occurrence = function(val) {
  return this.filter(e => e === val).length;
}

Use isso em qualquer lugar do seu projeto -

[1, 2, 4, 5, 2, 7, 2, 9].occurrence(2);
// above line returns 3
Rushikesh Bharad
fonte
0

Aqui está um liner em javascript.

  1. Use o mapa. Encontre os valores correspondentes (v === 2)na matriz, retornando uma matriz de uns e zeros.
  2. Use Reduzir. Adicione todos os valores da matriz para o número total encontrado.
[1, 2, 3, 5, 2, 8, 9, 2]
  .map(function(v) {
    return v === 2 ? 1 : 0;
  })
  .reduce((a, b) => a + b, 0);

O resultado é 3.

Jake
fonte
0

Dependendo de como você deseja executá-lo:

const reduced = (array, val) => { // self explanatory
    return array.filter((element) => element === val).length;
}

console.log(reduced([1, 2, 3, 5, 2, 8, 9, 2], 2));

// 3

const reducer = (array) => { // array to set > set.forEach > map.set
    const count = new Map();
    const values = new Set(array);
    values.forEach((element)=> {
        count.set(element, array.filter((arrayElement) => arrayElement === element).length);
    });
    return count;
}
console.log(reducer([1, 2, 3, 5, 2, 8, 9, 2]));

// Map(6) {1 => 1, 2 => 3, 3 => 1, 5 => 1, 8 => 1, …}
Mohammad Hassan
fonte
-7

Você pode usar a propriedade length na matriz JavaScript:

var myarray = [];
var count = myarray.length;//return 0

myarray = [1,2];
count = myarray.length;//return 2
Bhavik Bhavsar
fonte
Se você filtrá-lo primeiro, poderá usar o comprimento. Exemplo: array.filter (x => x === 2) .length
Scott Blanch