计算数组元素的出现次数/频率


215

在Javascript中,我试图获取数字值的初始数组并计算其中的元素。理想情况下,结果将是两个新数组,第一个数组指定每个唯一元素,第二个数组包含每个元素出现的次数。但是,我愿意接受有关输出格式的建议。

例如,如果初始数组为:

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

然后将创建两个新的数组。第一个将包含每个唯一元素的名称:

5, 2, 9, 4

第二个将包含元素在初始数组中出现的次数:

3, 5, 1, 1

因为数字5在初始数组中出现3次,所以数字2出现5次,而9和4都出现一次。

我已经寻找了很多解决方案,但是似乎没有任何效果,而且我尝试过的一切都变得异常复杂。任何帮助,将不胜感激!

谢谢 :)


8
如果只需要查看一个值是否仅出现一次(而不是两次或多次),则可以使用if (arr.indexOf(value) == arr.lastIndexOf(value))
Rodrigo

1
我们可以使用ramda.js简单的方法来实现这一目标。 const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]; R.countBy(r=> r)(ary)
Eshwar Prasad Yaddanapudi

arr.filter(x => x===5).length将返回3以指示数组中有3个5。
noobninja

Answers:


94

干得好:

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


24
具有对数组进行排序的副作用(副作用很严重),排序也很O(N log(N)),而且优雅的收获也不值得
ninjagecko 2011年

1
@ninja您还喜欢哪个其他答案?
西米·维达斯

如果没有第三方库提供的高级高级原语,我通常会像reduce答案一样实现。我要提交这样的答案,然后才能看到它已经存在。不过,counts[num] = counts[num] ? counts[num]+1 : 1答案也可以(相当于if(!result[a[i]])result[a[i]]=0答案,它更优雅但更不易读);可以修改此答案以使用for循环的“更精细”版本,也许是第三方的for循环,但是由于基于标准索引的for循环不幸是默认设置,因此我忽略了这一点。
ninjagecko 2011年

2
@忍者,我同意。这些答案更好。不幸的是,我不能不接受自己的回答。
西姆·维达斯

对于小型数组,原位排序比创建关联数组更快。
–quant_dev

219

您可以使用一个对象保存结果:

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

因此,现在您的计数对象可以告诉您特定数字的计数:

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

如果要获取成员数组,请使用keys()函数

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

3
应该指出的是,该Object.keys()功能仅在IE9 +,FF4 +,SF5 +,CH6 +中受支持,但Opera不支持。我认为这里最大的节目宣传站是IE9 +
罗伯特·科里特尼克

19
同样,我也喜欢counts[num] = (counts[num] || 0) + 1。这样一来,您只需要counts[num]在那一行上写两次,而不是三遍。
robru 2014年

1
这是一个很好的答案。这很容易抽象为一个接受数组并返回“ counts”对象的函数。
bitsand '17

对于问题中的特定示例而言,这是正确的,但是对于Google员工而言,值得指出的是,这并不总是一种安全的技术,可广泛使用。将这些值存储为对象键以对其进行计数意味着您将这些值转换为字符串,然后对该值进行计数。[5, "5"]只会说你有"5"两次。或计算实例中一些不同的对象只会告诉您很多[object Object]。等等,等等
金宝强尼

然后,我该如何过滤返回的对象以显示最高到最低,或最低到最高的数字?
Ryan Holton

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

39
acc[curr] ? acc[curr]++ : acc[curr] = 1;
2015年

谢谢,非常好的解决方案;)...并获取“键”和“值”数组:const keys = Object.keys(a); const values = Object.values(a);
ncenerar

79

如果使用下划线或破折号,这是最简单的操作:

_.countBy(array);

这样:

_.countBy([5, 5, 5, 2, 2, 2, 2, 2, 9, 4])
=> Object {2: 5, 4: 1, 5: 3, 9: 1}

正如其他人所指出的,然后可以对结果执行_.keys()_.values()函数,以分别获取唯一的数字及其出现的位置。但是根据我的经验,原始对象要容易处理得多。


55

不要为结果使用两个数组,而使用一个对象:

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
}

47

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构造函数,以创建唯一值的集合。然后,spread语法会将这些值扩展到一个新的数组中,以便我们可以调用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)将返回3aCount.get("5")将返回undefined。它还允许将任何值/类型用作键,这意味着该解决方案也可以与对象数组一起使用。


您是否有机会仅针对对象数组对此进行改进?我在尝试为对象数组修改它时遇到了麻烦,您只需在其中创建一个新的数组/映射/集即可在其中删除重复项,并为该对象添加一个新值,比如说称为“ duplicatedCount:value”。我设法从此答案中删除了嵌套对象数组中的重复项stackoverflow.com/a/36744732
sharon gur 17-4-4

Set使用对象引用具有唯一性,并且不提供用于比较“相似”对象的API 。如果要将此方法用于此类任务,则需要一些中间归约函数,以保证一系列唯一实例。这不是最有效的方法,但是我在这里整理了一个简单的例子
Emissary

感谢你的回答!但是我实际上以不同的方式解决了它。如果您可以看到我在此处添加的答案stackoverflow.com/a/43211561/4474900,请举我所做的示例。它运作良好,我的案例中有一个需要比较的复杂对象。不过,我不知道我的解决方案的效率
莎朗·古尔(Sharon gur)2017年

8
这可能会使用漂亮的新数据结构,但是在O()中具有运行时,而这里有许多简单的算法可以在O(n)中解决它。
raphinesse


34

一线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()])


现代Javascript
享誉全球-Igniter

29

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


3
有人愿意解释一下吗(prev [curr] = ++ prev [curr] || 1,prev)?
Souljacker

5
逗号操作 “评估每个操作数的(从左至右)并返回最后操作数的值”,所以这个增量分组[CURR(或初始化它为1)的值,然后返回分组。
ChrisV

但是输出是数组吗?
Francesco

20

如果您偏爱单一衬板。

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

编辑(6/12/2015):由内而外的解释。countMap是一个映射图,它以单词的频率映射单词,我们可以看到匿名函数。reduce所做的是将带有参数的函数应用为所有数组元素,并将countMap作为最后一个函数调用的返回值传递。最后一个参数({})是第一个函数调用的countMap的默认值。


1
你应该解释一下。这将是一个更好的答案,以便人们可以学习如何在其他用例中使用它。
Andrew Grothe 2015年

一个仅除去后面通常会出现的换行符的衬线;{}。... 好。我认为,有了一个单一班轮的定义,我们就可以将康威的《人生游戏》写成“独行侠”。
特里科特

16

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 }

一个Map而不是Plain Object可以帮助我们区分不同类型的元素,否则所有计数都基于字符串


8

如果您使用下划线,则可以使用功能路线

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/


8

基于答案@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},{})

));


它可以用来计算字符

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

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

));


如果您使用|| 0以下方法,它将更具可读性:(r,k)=>{r[k]=(r[k]||0)+1;return r}
12Me21

您可以在JavaScript中一行执行任何操作。
Ry-

为什么这是一件坏事,@ Ry-?
ESL

有时在几行中更清晰,而在另一行中更清晰。尽管是“品味”问题。
ESL

我的意思是“在ES6中您可以在一行中做到这一点”适用于每个答案,您也可以在ES5中在一行中做到这一点。
Ry-

5

这只是轻松轻巧的东西...

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

1
该问题要求对所有要素进行计数。

好的,错过了。现在应该修复。
ElDoRado1239

5

因此,这就是我将如何使用一些最新的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)

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

1
es6 / 7使这一切变得更好。您可能还想减少为a Map,因为这样可以避免使用数字作为对象键(作为字符串进行广播)进行类型转换。const answer = array.reduce((a, e) => a.set(e, (a.get(e) || 0) + 1), new Map())您可以获取answer.keys()键,并将answer.values()值作为数组获取。[...answer]将为您提供一个包含所有键/值的二维数组。
来自Qaribou的Josh,17年

4

具有reduce(固定)的ES6解决方案:

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

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


不确定像询问的问题一样,一个数字如何表示每个不同数组元素的计数。
Ry-

4

编辑2020年:这是一个相当老的答案(九年)。扩展本机prototype将始终引起讨论。尽管我认为程序员可以自由选择自己的编程风格,但这是一种(更现代的)解决问题的方法,而无需扩展Array.prototype

{
  // create array with some pseudo random values (1 - 5)
  const arr = Array.from({length: 100})
    .map( () => Math.floor(1 + Math.random() * 5) );
  // frequencies using a reducer
  const arrFrequencies = arr.reduce((acc, value) => 
      ({ ...acc, [value]: acc[value] + 1 || 1}), {} )
  console.log(`Value 4 occurs ${arrFrequencies[4]} times in arrFrequencies`);

  // bonus: restore Array from frequencies
  const arrRestored = Object.entries(arrFrequencies)
    .reduce( (acc, [key, value]) => acc.concat(Array(value).fill(+key)), [] );
  console.log(arrRestored.join());  
}
.as-console-wrapper { top: 0; max-height: 100% !important; }

旧的(2011)答案:您可以这样扩展Array.prototype


2

我的ramda解决方案:

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。




1

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


0

查看下面的代码。

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

0

试试这个:

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

0

我在代码战中解决了类似的问题,并设计了以下对我有用的解决方案。

这给出了数组中整数的最高计数,也给出了整数本身。我认为它也可以应用于字符串数组。

要正确排序字符串,请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;
}

0

这是一种计算对象数组中出现次数的方法。它还将第一个数组的内容放入新数组中以对值进行排序,以使原始数组中的顺序不被打乱。然后,使用递归函数遍历每个元素并计算数组内每个对象的数量属性。

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

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

每次复制对象都非常浪费。当它可能是线性的时,产生二次最坏情况。
Ry-

0
var aa = [1,3,5,7,3,2,4,6,8,1,3,5,5,2,0,6,5,9,6,3,5,2,5,6,8];
var newArray = {};
for(var element of aa){
  if(typeof newArray[element] === 'undefined' || newArray[element] === null){
    newArray[element] = 1;
  }else{
    newArray[element] +=1;
  }
}

for ( var element in newArray){
  console.log( element +" -> "+ newArray[element]);
}

0

这个问题已有8年以上的历史了,许多答案并没有真正考虑到ES6及其众多优点。

每当我们创建其他数组,制作数组的两倍或三倍副本,甚至将数组转换为对象时,考虑我们的代码对垃圾回收/内存管理的后果也许更为重要。这些对于小型应用程序来说是微不足道的观察,但是如果规模是一个长期目标,那么请仔细考虑这些。

如果您只需要针对特定​​数据类型的“计数器”,并且起点是数组(我想因此就需要一个有序列表,并利用数组提供的许多属性和方法),则只需遍历array1并填充array2的值和在array1中找到的这些值的出现次数。

就如此容易。

面向对象编程面向对象设计的简单类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;

无缘无故地将函数粘贴在类中不会使其成为面向对象的,finalList也没有理由成为数组,这与正确地执行它没有任何优势。
雷·

-1

这是计数数组的经典经典方法。

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

如果需要按字母顺序排列的结果,可以先对其进行排序,但是如果要保留输入数据的顺序,请尝试一下。嵌套循环可能比此页面上的某些其他方法慢一些。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.