Як порахувати певні елементи в масиві?


162

У мене є масив:

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

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

Який найелегантніший спосіб зробити це в JavaScript без циклу for?

Відповіді:


90

Дуже просто:

var count = 0;
for(var i = 0; i < array.length; ++i){
    if(array[i] == 2)
        count++;
}

53
Ні, що я маю на увазі, не маючи циклу "за"
Leem

9
@Leem: Чому циклічно шкідливо? Завжди є петельне в якийсь момент. Очевидно, ви створили б функцію, яка приховує цикл. Ці "я не хочу використовувати правильний інструмент для роботи" - запити ніколи не мали для мене великого сенсу. І ми можемо сперечатися, що найелегантніше. Наприклад, для виклику функції на один елемент просто порівняти його зі значенням не є елегантним.
Фелікс Клінг

2
for laughs: alar (eval ('(' + my_array.join ('== 2) + (') + '== 2)')) jsfiddle.net/gaby_de_wilde/gujbmych
користувач40521

29
ОП, напевно, вважає, що циклічне завершення - це погано, оскільки це 5 рядків коду та потребує змінного стану. Розробнику, який приходить прочитати, що пізніше доведеться витратити деякий час, щоб перевірити, що це робить, і втратити фокус від своєї задачі. Абстракція набагато вища: const count = countItems(array, 2);і деталі реалізації можна аргументувати всередині.
joeytwiddle

2
Це не правильна відповідь, оскільки в цьому питанні чітко задається питання про використання циклів. Перевірте моє рішення, яке не використовує циклів. stackoverflow.com/a/44743436/8211014
Луїс Орантес

289

[ ця відповідь трохи датована: прочитайте правки ]

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

(Я лише час від часу пишу for-loops у 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' python ' '), щоб уникнути необхідності підрахунку в першу чергу.

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. Якщо ви дбаєте про продуктивність, зауважте, що, хоча це асимптотично та ж продуктивність, що і час для циклу (O (N)), можливо, це потребуватиме O (N) додаткової пам'яті (замість O (1) пам'яті), оскільки це буде майже безумовно, генерують проміжний масив, а потім підраховують елементи цього проміжного масиву.


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)де список є масивом чисел. Ви також 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

Наступна одностайна функція стрілки (лямбда-функція) в JS:

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

може бути спрощено до цієї стислої форми для одного входу:

x => 2 * x

де returnмається на увазі.


Чи є функція фільтра ефективнішою, ніж використання es6 для циклу?
Ніклас

1
@Niklas, я думаю, що це те саме (оскільки обидва повинні перевірити всі елементи, 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);


8

Якщо ви використовуєте lodash або підкреслити, метод _.countBy надасть об'єкту сукупних підсумків, введених кожним значенням масиву. Ви можете перетворити це на однолінійний, якщо вам потрібно порахувати лише одне значення:

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

Це також добре працює на масивах чисел. Одним з лайнерів для вашого прикладу буде:

_.countBy([1, 2, 3, 5, 2, 8, 9, 2])[2]; // 3

5
Величезна надмірність. Так як це створює лічильники для всіх унікальних елементів. Витрачені запаси та час.
metalim

5

Найцікавіший спосіб, з якого я можу це зробити:

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

Де:

  • a - це масив
  • 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
*/

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)

Насправді, якщо ви дійсно хочете пофантазувати, ви можете створити функцію підрахунку на прототипі масиву. Потім ви можете його повторно використовувати.

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

1
Чи обробляє це порожній масив?
Ендрю Грімм

@AndrewGrimm має право. Базова справа - arr.length == 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);
}


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
Скотт Бланш
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.