3

Hi I've an array like this

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

my goal is to count the unique value and have a report about any items on it so the result's well be

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

I found a solution, that can be found in this other post [Counting the occurrences of JavaScript array elements

The @Emissary solution's for me is the best, the problem is that this solution go over and add some new function that I don't need and I can't reply directly to that post to ask how to have only the array that I need :D

@Emissary add the

console.log(key + ': ' + val)

My first idea was, instead console.log I can push every value in 2d array, but I don't think is a good idea, because if I have understand well the @Emissary solutions, the very 1st part of the solution create exactly the array that I need.

Any idea how to "isolate" this particular array?

Community
  • 1
  • 1
Jorman Franzini
  • 329
  • 1
  • 3
  • 17

7 Answers7

5

This is an ideal situation to use a Map because it identifies by key, but also turns into that type of array when cast to it:

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

var result = [...a.reduce( (m, v) => m.set(v, (m.get(v) || 0) + 1), new Map() )];

console.log(result);

Note that solutions which perform a.filter on every iteration, have O(n²) time complexity, while this is O(n). Instead of the spread operator you can use Array.from(a.reduce(....)).

For older browsers you can use this variant:

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

var obj = a.reduce( function (m, v) { return m[v] = (m[v] || 0) + 1, m }, {} ),
    result = [];
for (var key in obj) {
    result.push([+key, obj[key]]);
}
console.log(result);
trincot
  • 317,000
  • 35
  • 244
  • 286
  • 1
    Using `reduce` is the best solution in terms of performances. – loretoparisi Nov 17 '16 at 21:57
  • Tnx, seems to be quick, I only noticed that uses the spread syntax and I use an old browser Iceweasel and I don't know if can works. For now, tested only on node in windows, don't works for the spread sintax – Jorman Franzini Nov 17 '16 at 22:08
  • Which support do you need? ES3, ES5? Do you have support for `Map`? – trincot Nov 17 '16 at 22:11
  • So you think the spread operation is O(1)... In my experience `[...thingy]` is a terribly slow operation. – Redu Nov 17 '16 at 22:18
  • Tnx, I don't know exactly on which browser this will run, for now I'm testing the code locally on node. Your second solution works on node, but don't return a 2d array, only a normal array. I think I'll implement both solution with a try code. The goal is to have a very fast way, because the data is huge. – Jorman Franzini Nov 17 '16 at 22:23
  • @JormanFranzini, I fixed that to be a 2D array, maybe you need to refresh the page to see it. – trincot Nov 17 '16 at 22:24
  • @Redu, I never said the spread operator is *O(1)*. It is not. It is *O(n)*. NB: "Slow" is not a word that says anything about time complexity. – trincot Nov 17 '16 at 22:28
  • Tnx @trincot. The question is: is fast or is better the@Geeky solution? In terms of velocity. – Jorman Franzini Nov 17 '16 at 22:31
  • Well I'd just interpreted "Note that solutions which perform a.filter on every iteration, have O(n²) time complexity, while this is O(n)." sentence as spread to be O(1) since you already have a reduce stage. Sorry for misinterpretation. Your overall solution is O(n^2). And yes in the current versions of Chrome and FF `[...thingy]` is slower than `[].concat(thingy)` – Redu Nov 17 '16 at 22:34
  • @Redu, Why do you say *O(n²)*? It is not. *O(n) + O(n)* is still *O(n)*. You seem to multiply somewhere, but it that is not happening. First the reduce happens, then the spread. It is *O(n)*. – trincot Nov 17 '16 at 22:36
  • @JormanFranzini, Geek's solution is ES6 (it uses Maps), so now I am confused. Do you have Map support? Geek's solution is a more verbose version of my first solution. Like I wrote you can replace the spread operator with `Array.from`, depending on support. Some JavaScript engines show a slight performance difference between the two, but I have seen it go both ways. Just test what is best for you. – trincot Nov 17 '16 at 22:39
  • Tnx, I don't know which browser run this, is a multiplatform implementation so I've to consider the newest and oldest pc :D I only think that at this point performance are the key, I've to check which one is the fastest. Tnx – Jorman Franzini Nov 19 '16 at 09:44
3

You can do this with forEach() loop.

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

var result = []
a.forEach(function(e) {
  if (!this[e]) {
    this[e] = [e, 0];
    result.push(this[e])
  }
  this[e][1] ++
}, {})

console.log(result)
Nenad Vracar
  • 118,580
  • 15
  • 151
  • 176
2

You can do it using Mapmap of ES6 and Convert it back into array using Array.from array.from

Map is similar like hash,which maintains a key value pair. Instead of traversing each element and maintaining a count,create a map and set the count of each occurrence and convert it to an array which is easy with es6

check this snippet

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var myMap2 = new Map();
a.forEach(function(num) {
  if (myMap2.has(num)) {
    var count = myMap2.get(num);
    myMap2.set(num, count + 1);
  } else
    myMap2.set(num, 1);
});

var arr = Array.from(myMap2);
console.log(arr);

Hope it helps

Geeky
  • 7,420
  • 2
  • 24
  • 50
  • Tnx, this solution don't use the spread syntax and in my case is better because I need to grantee the best compatibility at all – Jorman Franzini Nov 17 '16 at 22:09
1

Just pull it out of the Map constructor

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
var aCount = [...new Set(a)].map(
  x => [x, a.filter(y => y === x).length]
);
console.log(aCount);
   
Alejandro C.
  • 3,771
  • 15
  • 18
0

You could use the spread syntax with the solution of Emissary to get the an array with the wanted result.

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];

var aCount = [... new Map([... new Set(a)].map(
    x => [x, a.filter(y => y === x).length]
))];
              
console.log(aCount);
Community
  • 1
  • 1
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
0

Good solutions here but just for variety you might as well do as follows while getting your results sorted

var a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4],
thing = a.reduce((p,c) => (p[c] ? p[c]++ : p[c] = 1,p),[])
         .reduce((p,c,i) => c ? (p.push([i,c]),p) : p ,[]);
console.log(thing);

Ok... and one might complain about the possibility of generating a huge sparse array depending on the original numbers but hey no worries... The for in loop takes care of that. So lets do it again...

var  a = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4],
sparse = a.reduce((p,c) => (p[c] ? p[c]++ : p[c] = 1,p),[]),
 dense = [];
for (var key in sparse) dense.push([+key,sparse[key]]);
console.log(dense);
Community
  • 1
  • 1
Redu
  • 25,060
  • 6
  • 56
  • 76
0

It's interesting to note that the reduce and spread syntax solution is also very efficient when evaluating array of words occurrences/frequency in a text (typically bag of words) when combined with the map to transform the output columns:

var r,words="We don't talk anymore\nWe don't talk anymore\nWe don't talk anymore\nLike we used to do\nWe don't love anymore\nWhat was all of it for?\nOh, we don't talk anymore\nLike we used to do\n\nI just heard you found the one you've been looking\nYou've been looking for"
words=words.split(/['\s]+/g);
r=[...words.reduce( (m, v) => m.set(v, ((m.get(v) || 0) + 1)), new Map() )].map(e=>[e[0],e[1]/words.length])
console.log(r)
loretoparisi
  • 15,724
  • 11
  • 102
  • 146