Как посчитать определенные элементы в массиве?

162

У меня есть массив:

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

Я хотел бы знать, сколько 2 s в массиве.

Каков самый элегантный способ сделать это в JavaScript без зацикливания с forциклом?

Лим
источник

Ответы:

90

Очень просто:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}
Тор Якобсен
источник
53
Нет, я имею в виду, без зацикливания с «для»
Leem
9
@Leem: Почему петли плохо? В любой момент всегда есть петля. Очевидно, вы бы создали функцию, которая скрывает цикл. Эти «я не хочу использовать правильный инструмент для работы» - запросы никогда не имели большого смысла для меня. И мы можем поспорить, что является самым элегантным. Например, для меня вызов функции для каждого элемента просто для сравнения его со значением не элегантен.
Феликс Клинг
2
для смеха: alert (eval ('(' + my_array.join ('== 2) + (') + '== 2)')) jsfiddle.net/gaby_de_wilde/gujbmych
user40521
29
ОП, вероятно, считает, что зацикливание плохое, потому что оно состоит из 5 строк кода и требует изменяемого состояния. Разработчик, который придет, чтобы прочитать это позже, должен будет потратить некоторое время, чтобы проверить, что он делает, и потерять фокус со своей задачей. Абстракция намного выше: const count = countItems(array, 2);детали реализации можно обсуждать внутри.
Joeytwiddle
2
Это неправильный ответ, потому что вопрос четко задан, чтобы не использовать циклы. Проверьте мое решение, которое не использует петли. stackoverflow.com/a/44743436/8211014
Луис Орантес
289

[ этот ответ немного устарел: читайте правки ]

Передай привет своим друзьям: mapи filterи reduceи forEachи everyт. Д.

(Я только иногда пишу for-loop в javascript, поскольку отсутствует область видимости на уровне блоков, поэтому вам все равно придется использовать функцию в качестве тела цикла, если вам нужно захватить или клонировать ваш итерационный индекс или значение. For-loops в целом более эффективны, но иногда требуется закрытие.)

Самый читаемый способ:

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

(Мы могли бы написать .filter(function(x){return x==2}).lengthвместо)

Ниже приведено более эффективное использование пространства (O (1), а не O (N)), но я не уверен, сколько из выгоды / штрафа вы могли бы заплатить с точки зрения времени (не более чем постоянный фактор, так как вы посещаете каждый элемент ровно один раз):

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

(Если вам нужно оптимизировать этот конкретный фрагмент кода, цикл for может быть быстрее в некоторых браузерах ... вы можете протестировать его на jsperf.com.)


Затем вы можете быть элегантным и превратить его в функцию-прототип:

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

Как это:

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

Вы также можете вставить обычный старый метод for-loop (см. Другие ответы) в приведенное выше определение свойства (опять же, это, вероятно, будет намного быстрее).


2017 редактировать :

К сожалению, этот ответ стал более популярным, чем правильный ответ. На самом деле, просто используйте принятый ответ. Хотя этот ответ может быть симпатичным, компиляторы js, вероятно, не (или не могут из-за спецификации) оптимизировать такие случаи. Таким образом, вы должны написать простой цикл:

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;
        }
    }
});

Вы можете определить версию, в .countStrictEq(...)которой используется ===понятие равенства. Понятие равенства может быть важно для того, что вы делаете! (например [1,10,3,'10'].count(10)==2, потому что числа типа «4» == 4 в javascript ... следовательно, вызывая его .countEqили .countNonstrictподчеркивая, он использует ==оператор.)

Также рассмотрите возможность использования вашей собственной многосетевой структуры данных (например, типа «питона collections.Counter»), чтобы избежать необходимости делать подсчет в первую очередь.

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 {}
    }
}

Демо-версия:

> 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)

sidenote: Хотя, если вы все еще хотите использовать функциональное программирование (или одноразовую однострочную компоновку без переопределения Array.prototype), вы можете написать ее более кратко в наши дни как [...].filter(x => x==2).length. Если вы заботитесь о производительности, обратите внимание, что, хотя это асимптотически та же производительность, что и цикл for (O (N) время), может потребоваться O (N) дополнительная память (вместо O (1) памяти), поскольку она почти конечно, создайте промежуточный массив и затем посчитайте элементы этого промежуточного массива.

ninjagecko
источник
1
Это хорошее решение FP, единственная «проблема» (для большинства случаев не имеет значения) создает промежуточный массив.
Tokland
1
@tokland: Если это проблема, вы можете это сделатьarray.reduce(function(total,x){return x==value? : total+1 : total}, 0)
ninjagecko
1
@ninjagecko Разве в тройном операторе не должно быть только одного двоеточия? [...].reduce(function(total,x){return x==2 ? total+1 : total}, 0)
А.Крюгер
2
@tokland Возможно, фильтр не создаст промежуточный массив. Хороший оптимизирующий компилятор может легко распознать, что используется только длина массива. Возможно, ни один из нынешних JS-компиляторов не достаточно умен, чтобы сделать это, но это не важно. Как говорит Фаулер: «Если что-то болит, то делай больше». Это недальновидно, чтобы избежать недостатков компилятора путем написания плохого кода. Если компилятор отстой, исправьте компилятор. mlafeldt.github.io/blog/if-it-hurts-do-it-more-often
Джон Хенкель
Я считаю это решение оптимальным +2017: const count = (list) => list.filter((x) => x == 2).length. Затем используйте его, позвонив, count(list)где list это массив чисел. Вы также const count = (list) => list.filter((x) => x.someProp === 'crazyValue').lengthможете подсчитать количество экземпляров crazyValue в массиве объектов. Обратите внимание, это точное соответствие для свойства.
agm1984
71

ES6 Обновление до JS:

Обратите внимание, что вы всегда должны использовать тройные равенства: ===чтобы получить правильное сравнение:

// 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

Следующая единственная функция Arrow (лямбда-функция) в JS:

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

может быть упрощено до этой краткой формы для одного ввода:

x => 2 * x

где returnподразумевается.

Сверриссон
источник
Является ли функция фильтра более производительной, чем использование цикла es6 for?
Никлас
1
@ Никлас, я думаю, что это то же самое (поскольку оба должны проверять все элементы, O (N)), но, я думаю, это зависит от браузера, а также от количества элементов и компьютера относительно того, что помещается в кеш-память. Итак, я думаю, ответ: «Это сложно» :)
Сверриссон
67

2017: Если кто-то все еще интересуется этим вопросом, мое решение заключается в следующем:

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
источник
8

Если вы используете lodash или подчеркивание, то метод _.countBy предоставит объект совокупных итогов, определяемый каждым значением в массиве. Вы можете превратить это в однострочник, если вам нужно только посчитать одно значение:

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

Это также хорошо работает на массивах чисел. Однострочником для вашего примера будет:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3
Coleman
источник
5
Огромное излишество. Как это создает счетчики для всех уникальных элементов. Потраченное впустую хранение и время.
Металим
5

Самый странный способ, которым я могу думать, это:

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

Куда:

  • а это массив
  • n - число для подсчета в массиве

Мое предложение, используйте время или для цикла ;-)

Гари Грин
источник
3

Отсутствие цикла обычно означает передачу процесса какому-либо методу, который использует цикл.

Вот способ, которым наш кодовый ненавидящий кодер может удовлетворить его отвращение по цене:

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

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


/*  returned value: (Number)
3
*/

Вы также можете повторно вызывать indexOf, если он доступен как метод массива, и каждый раз перемещать указатель поиска.

Это не создает новый массив, и цикл выполняется быстрее, чем forEach или фильтр.

Это может иметь значение, если у вас есть миллион членов, чтобы посмотреть.

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
источник
2
Вы могли бы сократить свое регулярное выражение до просто String(a).match(/2/g).length + 1- хотя остерегайтесь этого, иначе ваша реализация не будет хорошо работать с двузначными числами.
Гэри Грин
2
как насчет [2, 22, 2]?
Одуван
2

Большинство опубликованных решений, использующих функции массива, такие как фильтр, являются неполными, поскольку они не параметризованы.

Здесь идет решение, с помощью которого элемент для подсчета может быть установлен во время выполнения.

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);

Преимущество этого подхода заключается в том, что он может легко изменить функцию, чтобы подсчитать, например, количество элементов больше X.

Вы также можете объявить функцию приведения встроенной

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);
Луис Орантес
источник
var elementToFind=2; ... function (elementToFind, total, number){ return total += number==elementToFind; }.bind(this, elementToFind) ...труднее читать и не дает никаких преимуществ по сравнению с просто ... (acc, x) => acc += number == 2.... Мне нравится ваше использование +=вместо, acc + (number == 2)хотя. Похоже, необоснованный синтаксис HACK, хотя.
masterxilo
2

Действительно, зачем тебе это mapили filterдля этого? reduceбыл «рожден» для таких операций:

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

Это оно! (если item==valв каждой итерации к аккумулятору будет добавлено 1 count, как trueбудет разрешено 1).

Как функция:

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

Или продолжайте расширять свои массивы:

Array.prototype.count = function(val) {
   return this.reduce((count,item)=>count+(item==val),0)
}
Ювал А.
источник
2

Лучше обернуть это в функцию:

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

countNumber([1,2,3,4,5],3) // returns 1
Джастин Эррера
источник
2

Вот ES2017 + способ получить количество для всех элементов массива в 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;
});

Вы также можете при желании отсортировать вывод:

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

console.log (countsSorted) для вашего примера массива:

[
  [ '2', 3 ],
  [ '1', 1 ],
  [ '3', 1 ],
  [ '5', 1 ],
  [ '8', 1 ],
  [ '9', 1 ]
]
Яник Дж. Стейнбек
источник
1

Я считаю, что вы ищете, это функциональный подход

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

elem === 'a' - это условие, замените его своим.

Шантану Бхадория
источник
Он будет печатать не 3, а 0. Чтобы исправить это, ваша вторая строка должна быть count = arr.filter(elem => elem === 'a').lengthилиcount = arr.filter(elem => {return elem === 'a'}).length
WPomier
1

Я начинающий фанат функции снижения массива js.

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

На самом деле, если вы действительно хотите проявить фантазию, вы можете создать функцию count для прототипа Array. Тогда вы можете использовать его повторно.

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

Тогда делай

const myArray =[1, 2, 3, 5, 2, 8, 9, 2]
const count = myArray.count(x => x==2)
Скотт Бланш
источник
0

Решение рекурсией

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
источник
1
Это обрабатывает пустой массив?
Эндрю Гримм
@AndrewGrimm это правильно. Базовый случай: длина == 0
Джастин Майнерс,
Отличное решение! Я пытался сделать что-то, используя рекурсию только для практики, и ваш пример был более элегантным, чем то, что я делал. Это определенно более сложный способ, чем использование filter, reduceили простой forLoop, а также более дорогой, если смотреть на производительность, но все же отличный способ сделать это с помощью рекурсии. Мое единственное изменение: я просто думаю, что было бы лучше создать функцию и добавить в нее фильтр, чтобы скопировать массив и избежать мутации исходного массива, а затем использовать рекурсив в качестве внутренней функции.
Р. Маркес
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');
}

Я бы сделал что-то подобное:

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

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

  console.log(dd);
}

Roni
источник
0

Создайте новый метод для класса Array в файле уровня ядра и используйте его во всем проекте.

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

Используйте это где угодно в вашем проекте -

[1, 2, 4, 5, 2, 7, 2, 9].occurrence(2);
// above line returns 3
Рушикеш Бхарад
источник
0

Вот один вкладыш в JavaScript.

  1. Используйте карту. Найти совпадающие значения (v === 2)в массиве, возвращая массив единиц и нулей.
  2. Используйте Уменьшить. Добавьте все значения массива для общего числа найденных.
[1, 2, 3, 5, 2, 8, 9, 2]
  .map(function(v) {
    return v === 2 ? 1 : 0;
  })
  .reduce((a, b) => a + b, 0);

Результат есть 3.

Джейк
источник
0

В зависимости от того, как вы хотите его запустить:

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, …}
Мухаммед Хасан
источник
-7

Вы можете использовать свойство length в массиве JavaScript:

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

myarray = [1,2];
count = myarray.length;//return 2
Бхавик Бхавсар
источник
Если вы сначала отфильтруете его, то можете использовать длину. т.е. array.filter (x => x === 2) .length
Скотт