Подсчет вхождений/частоты элементов массива
В Javascript я пытаюсь взять начальный массив чисел и подсчитать элементы внутри него. В идеале результатом будет два новых массива, первый из которых определяет каждый уникальный элемент, а второй содержит количество раз, когда каждый элемент имеет место. Тем не менее, я открыт для предложений по формату вывода.
Например, если начальный массив был:
5, 5, 5, 2, 2, 2, 2, 2, 9, 4
Затем будут созданы два новых массива. Первый будет содержать имя каждого уникального элемента:
5, 2, 9, 4
Второй будет содержать количество раз, которое этот элемент произошел в исходном массиве:
3, 5, 1, 1
Поскольку число 5 встречается три раза в начальном массиве, число 2 происходит пять раз, а 9 и 4 появляются один раз.
Я много искал для решения, но ничего не работает, и все, что я пробовал, оказалось очень сложным. Любая помощь будет оценена!
Спасибо:)
Ответы
Ответ 1
Ну вот:
function foo(arr) {
var a = [], b = [], prev;
arr.sort();
for ( var i = 0; i < arr.length; i++ ) {
if ( arr[i] !== prev ) {
a.push(arr[i]);
b.push(1);
} else {
b[b.length-1]++;
}
prev = arr[i];
}
return [a, b];
}
Демоверсия в реальном времени: http://jsfiddle.net/simevidas/bnACW/
Заметка
Это меняет порядок исходного входного массива, используя Array.sort
Ответ 2
Вы можете использовать объект для хранения результатов:
var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var counts = {};
for (var i = 0; i < arr.length; i++) {
var num = arr[i];
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
console.log(counts[5], counts[2], counts[9], counts[4]);
Ответ 3
var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4].reduce(function (acc, curr) {
if (typeof acc[curr] == 'undefined') {
acc[curr] = 1;
} else {
acc[curr] += 1;
}
return acc;
}, {});
// a == {2: 5, 4: 1, 5: 3, 9: 1}
Ответ 4
Если использовать подчеркивание или lodash, это самая простая вещь:
_.countBy(array);
Такие, что:
_.countBy([5, 5, 5, 2, 2, 2, 2, 2, 9, 4])
=> Object {2: 5, 4: 1, 5: 3, 9: 1}
Как указывалось другими, вы можете затем выполнить функции _.keys()
и _.values()
в результате, чтобы получить только уникальные числа и их вхождения соответственно. Но, по моему опыту, с оригинальным объектом гораздо легче справиться.
Ответ 5
Не используйте два массива для результата, используйте объект:
a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
result = { };
for(var i = 0; i < a.length; ++i) {
if(!result[a[i]])
result[a[i]] = 0;
++result[a[i]];
}
Тогда result
будет выглядеть так:
{
2: 5,
4: 1,
5: 3,
9: 1
}
Ответ 6
Как насчет опции ECMAScript2015.
const a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
const aCount = new Map([...new Set(a)].map(
x => [x, a.filter(y => y === x).length]
));
aCount.get(5) // 3
aCount.get(2) // 5
aCount.get(9) // 1
aCount.get(4) // 1
Этот пример передает входной массив в конструктор Set
создавая коллекцию уникальных значений. Синтаксис распространения затем расширяет эти значения в новый массив, поэтому мы можем вызвать map
и перевести ее в двумерный массив пар [value, count]
- т.е. следующую структуру:
Array [
[5, 3],
[2, 5],
[9, 1],
[4, 1]
]
Затем новый массив передается конструктору Map
приводящему к итерабельному объекту:
Map {
5 => 3,
2 => 5,
9 => 1,
4 => 1
}
Самое замечательное в объекте Map
заключается в том, что он сохраняет типы данных, то есть aCount.get(5)
вернет 3
но aCount.get("5")
вернет undefined
. Он также позволяет любому значению/типу действовать как ключ, означающий, что это решение также будет работать с массивом объектов.
function frequencies(/* {Array} */ a){
return new Map([...new Set(a)].map(
x => [x, a.filter(y => y === x).length]
));
}
let foo = { value: 'foo' },
bar = { value: 'bar' },
baz = { value: 'baz' };
let aNumbers = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4],
aObjects = [foo, bar, foo, foo, baz, bar];
frequencies(aNumbers).forEach((val, key) => console.log(key + ': ' + val));
frequencies(aObjects).forEach((val, key) => console.log(key.value + ': ' + val));
Ответ 7
Я думаю, что это самый простой способ подсчета вхождений с одинаковым значением в массиве.
var a = [true, false, false, false];
a.filter(function(value){
return value === false;
}).length
Ответ 8
const data = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
function count(arr) {
return arr.reduce((prev, curr) => (prev[curr] = ++prev[curr] || 1, prev), {})
}
console.log(count(data))
Ответ 9
Если вы пользуетесь одним лайнером.
arr.reduce(function(countMap, word) {countMap[word] = ++countMap[word] || 1;return countMap}, {});
Изменить (6/12/2015):
Объяснение изнутри.
countMap - это карта, которая отображает слово с его частотой, что позволяет увидеть анонимную функцию. Что уменьшает, применяется функция с аргументами, так как все элементы массива и countMap передаются как возвращаемое значение последнего вызова функции. Последним параметром ({}) является значение по умолчанию countMap для первого вызова функции.
Ответ 10
Версия ES6 должна быть значительно упрощенной (другое однострочное решение)
let arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
let acc = arr.reduce((acc, val) => acc.set(val, 1 + (acc.get(val) || 0)), new Map());
console.log(acc);
// output: Map { 5 => 3, 2 => 5, 9 => 1, 4 => 1 }
Карта вместо простого объекта помогает нам различать разные типы элементов, иначе все подсчеты основаны на строках
Ответ 11
Если вы используете подчеркивание, вы можете перейти к функциональному маршруту
a = ['foo', 'foo', 'bar'];
var results = _.reduce(a,function(counts,key){ counts[key]++; return counts },
_.object( _.map( _.uniq(a), function(key) { return [key, 0] })))
поэтому ваш первый массив
_.keys(results)
а второй массив
_.values(results)
большинство из них по умолчанию будут использовать встроенные функции javascript, если они доступны
demo: http://jsfiddle.net/dAaUU/
Ответ 12
На основе answer @adamse и @pmandell (который я повышаю), в ES6 вы можете сделать это в одной строке:
- 2017 edit. Я использую
||
, чтобы уменьшить размер кода и сделать его более читаемым.
var a=[7,1,7,2,2,7,3,3,3,7,,7,7,7];
alert(JSON.stringify(
a.reduce((r,k)=>{r[k]=1+r[k]||1;return r},{})
));
Ответ 13
Вы можете расширить прототип массива, например:
Array.prototype.frequencies = function() {
var l = this.length, result = {all:[]};
while (l--){
result[this[l]] = result[this[l]] ? ++result[this[l]] : 1;
}
// all pairs (label, frequencies) to an array of arrays(2)
for (var l in result){
if (result.hasOwnProperty(l) && l !== 'all'){
result.all.push([ l,result[l] ]);
}
}
return result;
};
var freqs = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4].frequencies();
alert(freqs[2]); //=> 5
// or
var freqs = '1,1,2,one,one,2,2,22,three,four,five,three,three,five'
.split(',')
.frequencies();
alert(freqs.three); //=> 3
В качестве альтернативы вы можете использовать Array.map
:
Array.prototype.frequencies = function () {
var freqs = {sum: 0};
this.map( function (a){
if (!(a in this)) { this[a] = 1; }
else { this[a] += 1; }
this.sum += 1;
return a; }, freqs
);
return freqs;
}
Ответ 14
Здесь просто что-то легкое и легкое для глаз...
function count(a,i){
var result = 0;
for(var o in a)
if(a[o] == i)
result++;
return result;
}
Изменить: И так как вам нужны все вхождения...
function count(a){
var result = {};
for(var i in a){
if(result[a[i]] == undefined) result[a[i]] = 0;
result[a[i]]++;
}
return result;
}
Ответ 15
Итак, вот как я мог бы сделать это с некоторыми из новейших функций JavaScript:
Сначала уменьшите массив до Map
подсчетов:
let countMap = array.reduce(
(map, value) => {map.set(value, (map.get(value) || 0) + 1); return map},
new Map()
)
Используя Map
, ваш начальный массив может содержать любой тип объекта, и количество будет правильным. Без Map
некоторые типы объектов будут давать странные подсчеты. См. Map
документов для получения дополнительной информации о различиях.
Это также может быть сделано с объектом, если все ваши значения являются символами, числами или строками:
let countObject = array.reduce(
(map, value) => { map[value] = (map[value] || 0) + 1; return map },
{}
)
Или немного более функционально без мутаций, используя деструктуризацию и синтаксис распространения объектов:
let countObject = array.reduce(
(value, {[value]: count = 0, ...rest}) => ({ [value]: count + 1, ...rest }),
{}
)
На этом этапе вы можете использовать Map
или объект для подсчета (и карта является итеративной, в отличие от объекта) или преобразовать ее в два массива.
Для Map
:
countMap.forEach((count, value) => console.log('value: ${value}, count: ${count}')
let values = countMap.keys()
let counts = countMap.values()
Или для объекта:
Object
.entries(countObject) // convert to array of [key, valueAtKey] pairs
.forEach(([value, count]) => console.log('value: ${value}, count: ${count}')
let values = Object.keys(countObject)
let counts = Object.values(countObject)
Ответ 16
Однолинейное решение ES6. Так много ответов, использующих объект как карту, но я не вижу никого, кто бы использовал реальную карту
const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());
Используйте map.keys()
, чтобы получить уникальные элементы
Используйте map.values()
, чтобы получить события
Используйте map.entries()
, чтобы получить пары [элемент, частота]
Я немного опоздал на вечеринку, но надеюсь, что хотя бы один человек сочтет это полезным.
var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());
console.info([...map.keys()])
console.info([...map.values()])
console.info([...map.entries()])
Ответ 17
var array = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
function countDuplicates(obj, num){
obj[num] = (++obj[num] || 1);
return obj;
}
var answer = array.reduce(countDuplicates, {});
// answer => {2:5, 4:1, 5:3, 9:1};
Если вам все еще нужны массивы два, вы можете использовать ответ так:
var uniqueNums = Object.keys(answer);
// uniqueNums => ["2", "4", "5", "9"];
var countOfNums = Object.keys(answer).map(key => answer[key]);
// countOfNums => [5, 1, 3, 1];
Или если вы хотите, чтобы uniqueNums были цифрами
var uniqueNums = Object.keys(answer).map(key => +key);
// uniqueNums => [2, 4, 5, 9];
Ответ 18
Решение ES6 с уменьшенным (фиксированным):
const arr = [2, 2, 2, 3, 2]
const count = arr.reduce((pre, cur) => (cur === 2) ? ++pre : pre, 0)
console.log(count) // 4
Ответ 19
Мое решение с рамдой:
const testArray = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
const counfFrequency = R.compose(
R.map(R.length),
R.groupBy(R.identity),
)
counfFrequency(testArray)
Ссылка на REPL.
Ответ 20
Ознакомьтесь с приведенным ниже кодом.
<html>
<head>
<script>
// array with values
var ar = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var Unique = []; // we'll store a list of unique values in here
var Counts = []; // we'll store the number of occurances in here
for(var i in ar)
{
var Index = ar[i];
Unique[Index] = ar[i];
if(typeof(Counts[Index])=='undefined')
Counts[Index]=1;
else
Counts[Index]++;
}
// remove empty items
Unique = Unique.filter(function(){ return true});
Counts = Counts.filter(function(){ return true});
alert(ar.join(','));
alert(Unique.join(','));
alert(Counts.join(','));
var a=[];
for(var i=0; i<Unique.length; i++)
{
a.push(Unique[i] + ':' + Counts[i] + 'x');
}
alert(a.join(', '));
</script>
</head>
<body>
</body>
</html>
Ответ 21
Попробуйте следующее:
Array.prototype.getItemCount = function(item) {
var counts = {};
for(var i = 0; i< this.length; i++) {
var num = this[i];
counts[num] = counts[num] ? counts[num]+1 : 1;
}
return counts[item] || 0;
}
Ответ 22
Просто другая точка зрения...
let array = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
let count = 0
let finalResult = []
array.sort().map((e, index)=>{
if(e==array[index+1]){
count++
} else {
count++
finalResult.push([e+" => "+count])
count = 0
}
})
console.log(finalResult)
// output
// [ [ '2 => 5' ], [ '4 => 1' ], [ '5 => 3' ], [ '9 => 1' ] ]
Ответ 23
Вот самое простое решение
const data = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
let occurance_arr=[];
const aCount = [...new Set(data)].map(x => {
occurance_arr.push(data.filter(y=> y==x).length)
});
console.log(occurance_arr); //[3, 5, 1, 1]
Ответ 24
Этому вопросу более 8 лет, и многие, многие ответы на самом деле не учитывают ES6 и его многочисленные преимущества.
Возможно, еще важнее подумать о последствиях нашего кода для сбора мусора/управления памятью всякий раз, когда мы создаем дополнительные массивы, делаем двойные или тройные копии массивов или даже конвертируем массивы в объекты. Это тривиальные наблюдения для небольших приложений, но если масштаб является долгосрочной целью, подумайте об этом тщательно.
Если вам просто нужен "счетчик" для определенных типов данных, а отправной точкой является массив (поэтому я предполагаю, что вы хотите упорядоченный список и воспользоваться множеством свойств и методов, предлагаемых массивами), вы можете просто перебрать массив1 и заполнить массив2 со значениями и количеством вхождений для этих значений, найденных в массиве1.
Так просто.
Пример простого класса SimpleCounter (ES6) для объектно-ориентированного программирования и объектно-ориентированного проектирования
class SimpleCounter {
constructor(rawList){ // input array type
this.rawList = rawList;
this.finalList = [];
}
mapValues(){ // returns a new array
this.rawList.forEach(value => {
this.finalList[value] ? this.finalList[value]++ : this.finalList[value] = 1;
});
this.rawList = null; // remove array1 for garbage collection
return this.finalList;
}
}
module.exports = SimpleCounter;
Ответ 25
Я решал аналогичную проблему на кодовом компьютере и разработал следующее решение, которое сработало для меня.
Это дает наивысший счетчик целого числа в массиве, а также целое число. Я думаю, что это можно применить и к массиву строк.
Чтобы правильно отсортировать строки, удалите function(a, b){return a-b}
из части sort()
function mostFrequentItemCount(collection) {
collection.sort(function(a, b){return a-b});
var i=0;
var ans=[];
var int_ans=[];
while(i<collection.length)
{
if(collection[i]===collection[i+1])
{
int_ans.push(collection[i]);
}
else
{
int_ans.push(collection[i]);
ans.push(int_ans);
int_ans=[];
}
i++;
}
var high_count=0;
var high_ans;
i=0;
while(i<ans.length)
{
if(ans[i].length>high_count)
{
high_count=ans[i].length;
high_ans=ans[i][0];
}
i++;
}
return high_ans;
}
Ответ 26
Существует намного лучший и простой способ сделать это с помощью ramda.js
.
Пример кода здесь
const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
R.countBy(r=> r)(ary)
документация countBy находится в документации
Ответ 27
В отношении моего комментария, спрашивающего @Emissary о корректировке его решения. я добавляю способ, которым я его обрабатывал:
let distinctArr = yourArray.filter((curElement, index, array) => array.findIndex(t => t.prop1=== curElement.prop1 && t.prop2 === curElement.prop2 && t.prop3=== curElement.prop3) === index);
let distinctWithCount = [...new Set(distinctArr)].map(function(element){element.prop4 = yourArray.filter(t => t.prop1=== element.prop1 && t.prop2 === element.prop2 && t.prop2=== element.prop2).length;
То, что я делаю здесь, - это первое удаление дубликатов и сохранение массива (distinctArr), а затем im, рассчитывающий на исходный массив (yourArray) количество времени, в течение которого объект дублировался, и добавление 4-го свойства со значением вхождения
Надеюсь, что это поможет кому-то, нуждающемуся в этом конкретном решении
Ofc это сделано с ES6
Ответ 28
Вот способ подсчета вхождений внутри массива объектов. Он также помещает первое содержимое массива в новый массив для сортировки значений, чтобы порядок в исходном массиве не был нарушен. Затем рекурсивная функция используется для прохождения каждого элемента и подсчета свойства количества каждого объекта внутри массива.
var big_array = [
{ name: "Pineapples", quantity: 3 },
{ name: "Pineapples", quantity: 1 },
{ name: "Bananas", quantity: 1 },
{ name: "Limes", quantity: 1 },
{ name: "Bananas", quantity: 1 },
{ name: "Pineapples", quantity: 2 },
{ name: "Pineapples", quantity: 1 },
{ name: "Bananas", quantity: 1 },
{ name: "Bananas", quantity: 1 },
{ name: "Bananas", quantity: 5 },
{ name: "Coconuts", quantity: 1 },
{ name: "Lemons", quantity: 2 },
{ name: "Oranges", quantity: 1 },
{ name: "Lemons", quantity: 1 },
{ name: "Limes", quantity: 1 },
{ name: "Grapefruit", quantity: 1 },
{ name: "Coconuts", quantity: 5 },
{ name: "Oranges", quantity: 6 }
];
function countThem() {
var names_array = [];
for (var i = 0; i < big_array.length; i++) {
names_array.push( Object.assign({}, big_array[i]) );
}
function outerHolder(item_array) {
if (item_array.length > 0) {
var occurrences = [];
var counter = 0;
var bgarlen = item_array.length;
item_array.sort(function(a, b) { return (a.name > b.name) ? 1 : ((b.name > a.name) ? -1 : 0); });
function recursiveCounter() {
occurrences.push(item_array[0]);
item_array.splice(0, 1);
var last_occurrence_element = occurrences.length - 1;
var last_occurrence_entry = occurrences[last_occurrence_element].name;
var occur_counter = 0;
var quantity_counter = 0;
for (var i = 0; i < occurrences.length; i++) {
if (occurrences[i].name === last_occurrence_entry) {
occur_counter = occur_counter + 1;
if (occur_counter === 1) {
quantity_counter = occurrences[i].quantity;
} else {
quantity_counter = quantity_counter + occurrences[i].quantity;
}
}
}
if (occur_counter > 1) {
var current_match = occurrences.length - 2;
occurrences[current_match].quantity = quantity_counter;
occurrences.splice(last_occurrence_element, 1);
}
counter = counter + 1;
if (counter < bgarlen) {
recursiveCounter();
}
}
recursiveCounter();
return occurrences;
}
}
alert(JSON.stringify(outerHolder(names_array)));
}
Ответ 29
function countOcurrences(arr){
return arr.reduce((aggregator, value, index, array) => {
if(!aggregator[value]){
return aggregator = {...aggregator, [value]: 1};
}else{
return aggregator = {...aggregator, [value]:++aggregator[value]};
}
}, {})
}
Ответ 30
Используя MAP, вы можете иметь 2 массива в выходных данных: один содержит вхождения, а другой - количество вхождений.
const dataset = [2,2,4,2,6,4,7,8,5,6,7,10,10,10,15];
let values = [];
let keys = [];
var mapWithOccurences = dataset.reduce((a,c) => {
if(a.has(c)) a.set(c,a.get(c)+1);
else a.set(c,1);
return a;
}, new Map())
.forEach((value, key, map) => {
keys.push(key);
values.push(value);
});
console.log(keys)
console.log(values)