Подсчет вхождений / частоты элементов массива

в Javascript я пытаюсь взять начальный массив числовых значений и подсчитать элементы внутри него. В идеале результатом будет два новых массива, первый из которых будет указывать каждый уникальный элемент, а второй-количество раз, когда происходит каждый элемент. Тем не менее, я открыт для предложений по формату вывода.

например, если исходный массив был:

5, 5, 5, 2, 2, 2, 2, 2, 9, 4

затем будут созданы два новых массива. Первый будет содержать имя каждого уникального элемент:

5, 2, 9, 4

второй будет содержать количество раз, когда этот элемент произошел в исходном массиве:

3, 5, 1, 1

поскольку число 5 встречается в исходном массиве три раза, число 2 встречается пять раз, а 9 и 4 появляются один раз.

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

спасибо :)

26 ответов


здесь вы идете:

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/


вы можете использовать объект для хранения результатов:

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

Итак, теперь ваш объект counts может сказать вам, что такое count для определенного числа:

console.log(counts[5]); // logs '3'

если вы хотите получить массив членов, просто используйте keys() функции

keys(counts); // returns ["5", "2", "9", "4"]

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}

если вы используете подчеркивание или 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() функции на результат, чтобы получить только уникальные номера, и их состояниях, соответственно. Но, по моему опыту, с первоначальным объектом гораздо легче иметь дело.


не используйте два массива для результата, используйте объект:

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
}

как насчет опции 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 конструктор создание коллекции уникальный значения. The распространение синтаксис затем расширяет эти значения в новый массив, чтобы мы могли вызвать map и перевести это в двумерный массив [value, count] пары-т. е. следующая структура:

Array [
   [5, 3],
   [2, 5],
   [9, 1],
   [4, 1]
]

новый массив затем передается в Map конструктор в результате чего типа Iterable


Я думаю, что это самый простой способ подсчета вхождений с одинаковым значением в массиве.

var a = [true, false, false, false];
a.filter(function(value){
    return value === false;
}).length

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

Если вы предпочитаете один лайнер.

arr.reduce(function(countMap, word) {countMap[word] = ++countMap[word] || 1;return countMap}, {});

Edit (6/12/2015): Объяснение изнутри наружу. countMap-это карта, которая отображает слово с его частотой, которую мы можем видеть анонимную функцию. Reduce применяет функцию с аргументами как все элементы массива, а countMap передается как возвращаемое значение последнего вызова функции. Последний параметр ({}) является значением по умолчанию countMap для первого вызова функции.


Если вы используете подчеркивание, вы можете перейти на функциональный маршрут

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, если они доступны

демо:http://jsfiddle.net/dAaUU/


на основе ответ of @adamse и @pmandell (который я upvote), в 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},{})

));

его можно использовать для подсчет символов:

var s="ABRACADABRA";
alert(JSON.stringify(

s.split('').reduce((a, c)=>{a[c]++?0:a[c]=1;return a},{})

));

версия 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 }

карта вместо простого объекта помогает нам различать различные типы элементов, иначе все подсчеты основаны на строках


вы можете расширить прототип массива, например:

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

вот только что-то светлое и легкое для глаз...

function count(a,i){
 var result = 0;
 for(var o in a)
  if(a[o] == i)
   result++;
 return result;
}

Edit: и так как вы хотите все вхождения...

function count(a){
 var result = {};
 for(var i in a){
  if(result[a[i]] == undefined) result[a[i]] = 0;
  result[a[i]]++;
 }
 return result;
}

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

решение ES6 с уменьшением (исправлено):

const arr = [2, 2, 2, 3, 2]

const count = arr.reduce((pre, cur) => (cur === 2) ? ++pre : pre, 0)
console.log(count) // 4

проверьте код ниже.

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

попробуйте это:

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

вы можете сделать это намного проще, расширив свои массивы с помощью


заданный массив x я.е x = ['boy','man','oldman','scout','pilot']; количество вхождений элемента 'man' и

x.length - x.toString().split(',man,').toString().split(',').length ;

я решал аналогичную проблему на codewars и разработал следующее решение, которое сработало для меня.

Это дает наибольшее количество целого числа в массиве, а также само целое число. Я думаю, что его можно применить и к string array.

чтобы правильно отсортировать строки, удалите 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;
}

существует гораздо лучший и простой способ сделать это с помощью ramda.js. пример кода здесь

const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]; R.countBy(r=> r)(ary) документация countBy находится в документация


в отношении моего комментария, спрашивающего @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;

то, что я делаю здесь, 1-е удаление дубликатов и сохранение массива ( distinctArr), затем я рассчитываю на исходный массив (yourArray) количество времени, в течение которого объект был дублирован, и добавление 4-го свойства со значением вхождений

надеюсь, что это поможет кому-то, кто нуждается в этом конкретном решении Ofc он сделан с ЕС6


вот способ подсчета вхождений в массив объектов. Он также помещает содержимое первого массива в новый массив для сортировки значений, чтобы порядок в исходном массиве не нарушался. Затем рекурсивная функция используется для прохождения каждого элемента и подсчета свойства quantity каждого объекта внутри массива.

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

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]};
      }
    }, {})
}

вот классический метод старой школы для подсчета массивов.

var arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var counted = [], count = [];
var i = 0, j = 0, k = 0;
while (k < arr.length) {
    if (counted.indexOf(arr[k]) < 0) {
        counted[i] = arr[k];
        count[i] = 0;
        for (j = 0; j < arr.length; j++) {
            if (counted[i] == arr[j]) {
                count[i]++;
            }
        }
        i++;
    } else {
        k++;
    }
}

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