How can I sum all unique numbers of an array? - javascript

I've the following data:
var array = [2, 4, 12, 4, 3, 2, 2, 5, 0];
I want to sum 2 + 4 + 12 + 3 + 5 and want to show the result using JavaScript without any library, by simply using a for loop and if/else statements.

You can make use of ES6 Sets and .reduce() method of arrays:
let array = [2, 4, 12, 4, 3, 2, 2, 5, 0];
let sum = [...new Set(array)].reduce((a, c) => (a + c), 0);
console.log(sum);

you can try following using Set and Array.reduce
var array = [2,4,12,4,3,2,2,5,0];
let set = new Set();
let sum = array.reduce((a,c) => {
if(!set.has(c)) {set.add(c); a += c; }
return a;
}, 0);
console.log(sum);

const distinct = (array) =>
array ? array.reduce((arr, item) => (arr.find(i => i === item) ? [...arr] : [...arr, item]), []) : array;
const sum = distinct([2,4,12,4,3,2,2,5,0]).reduce((a,b) => a + b);
console.log(sum);

Here is a simple solution using a loop:
const array = [2, 4, 12, 4, 3, 2, 2, 5, 0];
function sumNotCommon(array) {
const sortedArray = array.slice().sort();
let sum = 0;
for (let i = 0; i < sortedArray.length; ++i) {
if (i === 0 || sortedArray[i] !== sortedArray[i-1])
sum += sortedArray[i];
}
return sum;
}
console.log(sumNotCommon(array));
First it sorts the array and then iterates through it ignoring equal numbers that follow each other.

Related

Sum of similar value in n X n dimensional array with n^2 complexity

Given an array [[1, 7, 3, 8],[3, 2, 9, 4],[4, 3, 2, 1]],
how can I find the sum of its repeating elements? (In this case, the sum would be 10.)
Repeated values are - 1 two times, 3 three times, 2 two times, and 4 two times
So, 1 + 3 + 2 + 4 = 10
Need to solve this problem in the minimum time
There are multiple ways to solve this but time complexity is a major issue.
I try this with the recursion function
How can I optimize more
`
var uniqueArray = []
var sumArray = []
var sum = 0
function sumOfUniqueValue (num){
for(let i in num){
if(Array.isArray(num[i])){
sumOfUniqueValue(num[i])
}
else{
// if the first time any value will be there then push in a unique array
if(!uniqueArray.includes(num[i])){
uniqueArray.push(num[i])
}
// if the value repeats then check else condition
else{
// we will check that it is already added in sum or not
// so for record we will push the added value in sumArray so that it will added in sum only single time in case of the value repeat more then 2 times
if(!sumArray.includes(num[i])){
sumArray.push(num[i])
sum+=Number(num[i])
}
}
}
}
}
sumOfUniqueValue([[1, 7, 3, 8],[1, 2, 9, 4],[4, 3, 2, 7]])
console.log("Sum =",sum)
`
That's a real problem, I am just curious to solve this problem so that I can implement it in my project.
If you guys please mention the time it will take to complete in ms or ns then that would be really helpful, also how the solution will perform on big data set.
Thanks
I would probably use a hash table instead of an array search with .includes(x) instead...
And it's also possible to use a classical for loop instead of recursive to reduce call stack.
function sumOfUniqueValue2 (matrix) {
const matrixes = [matrix]
let sum = 0
let hashTable = {}
for (let i = 0; i < matrixes.length; i++) {
let matrix = matrixes[i]
for (let j = 0; j < matrix.length; j++) {
let x = matrix[j]
if (Array.isArray(x)) {
matrixes.push(x)
} else {
if (hashTable[x]) continue;
if (hashTable[x] === undefined) {
hashTable[x] = false;
continue;
}
hashTable[x] = true;
sum += x;
}
}
}
return sum
}
const sum = sumOfUniqueValue2([[1, 7, 3, 8],[[[[[3, 2, 9, 4]]]]],[[4, 3, 2, 1]]]) // 10
console.log("Sum =", sum)
This is probably the fastest way...
But if i could choose a more cleaner solution that is easier to understand then i would have used flat + sort first, chances are that the built in javascript engine can optimize this routes instead of running in the javascript main thread.
function sumOfUniqueValue (matrix) {
const numbers = matrix.flat(Infinity).sort()
const len = numbers.length
let sum = 0
for (let i = 1; i < len; i++) {
if (numbers[i] === numbers[i - 1]) {
sum += numbers[i]
for (i++; i < len && numbers[i] === numbers[i - 1]; i++);
}
}
return sum
}
const sum = sumOfUniqueValue2([[1, 7, 3, 8],[[[[[3, 2, 9, 4]]]]],[[4, 3, 2, 1]]]) // 10
console.log("Sum =", sum)
You could use an objkect for keeping trak of seen values, like
seen[value] = undefined // value is not seen before
seen[value] = false // value is not counted/seen once
seen[value] = true // value is counted/seen more than once
For getting a value, you could take two nested loops and visit every value.
Finally return sum.
const
sumOfUniqueValue = (values, seen = {}) => {
let sum = 0;
for (const value of values) {
if (Array.isArray(value)) {
sum += sumOfUniqueValue(value, seen);
continue;
}
if (seen[value]) continue;
if (seen[value] === undefined) {
seen[value] = false;
continue;
}
seen[value] = true;
sum += value;
}
return sum;
},
sum = sumOfUniqueValue([[1, 7, 3, 8], [3, 2, 9, 4], [4, 3, 2, 1]]);
console.log(sum);
Alternatively take a filter and sum the values. (it could be more performat with omitting same calls.)
const
data = [[1, 7, 3, 8], [3, 2, 9, 4, 2], [4, 3, 2, 1]],
sum = data
.flat(Infinity)
.filter((v, i, a) => a.indexOf(v) !== a.lastIndexOf(v) && i === a.indexOf(v))
.reduce((a, b) => a + b, 0);
console.log(sum);
You can flatten the array, filter-out single-instance values, and sum the result:
const data = [
[ 1, 7, 3, 8 ],
[ 3, 2, 9, 4 ],
[ 4, 3, 2, 1 ]
];
const numbers = new Set( data.flat(Infinity).filter(
(value, index, arr) => arr.lastIndexOf(value) != index)
);
const sum = [ ...numbers ].reduce((a, b) => a + b, 0);
Another approach could be the check the first and last index of the number in a flattened array, deciding whether or not it ought to be added to the overall sum:
let sum = 0;
const numbers = data.flat(Infinity);
for ( let i = 0; i < numbers.length; i++ ) {
const first = numbers.indexOf( numbers[ i ] );
const last = numbers.lastIndexOf( numbers[ i ] );
if ( i == first && i != last ) {
sum = sum + numbers[ i ];
}
}
// Sum of numbers in set
console.log( sum );

What is the efficient way to find an array inside an array?

Find an Array of Values Inside an Array
Lets say I have an array [1,2,3,8,2,3,4,5,6,7,8] and I want to find the first occurrence of the values [3,4,5,6] together, how might I do that? I can use Array.prototype.findIndex, but when I am looking for a large amount of values in a large array, it doesn't feel like the proper way to do it.
What fails:
let largeArray = [1,2,3,8,2,3,4,5,6,7,8];
let smallArray = [3,4,5,6];
//Problem: an array isn't a function
largeArray.findIndex(smallArray);
/*
Problem: always returns -1 because it checks each element
rather than looking for a group of elements.
*/
largeArray.indexOf(smallArray);
//Problem: same as using indexOf
largeArray.findIndex(item=>smallArray);
What works:
let largeArray = [1,2,3,8,2,3,4,5,6,7,8];
let smallArray = [3,4,5,6];
//Here is what works, but isn't ideal
largeArray.findIndex((item, index, arr) => {
let isTheOne = item == smallArray[0] &&
arr[index + 1] == smallArray[1] &&
arr[index + 2] == smallArray[2] &&
arr[index + 3] == smallArray[3];
return isTheOne;
});
//It returns 5, which is correct.
To Be Continued
I am currently using what works, but what if largeArray had the length of a million and smallArray had the length of 300. That would be 1 line of item == smallArray[0] &&, 298 lines of arr[index + x] == smallArray[x] &&, and 1 line of arr[index + x] == smallArray[x];. I don't want to use Array.prototype.map, Array.prototype.filter, Array.prototype.forEach, a for loop, or a while loop. This is because Array.prototype.map, Array.prototype.forEach, and the loops take a very long time to complete. I don't want to use Array.prototype.filter because that doesn't give me the index.
You were on the right track, you just want to use every() to look over the small index to check that each index matches
const largeArray = [1, 2, 3, 8, 2, 3, 4, 5, 6, 7, 8];
let smallArray = [3, 4, 5, 6];
const index = largeArray.findIndex(
(item, index, arr) =>
smallArray.every(
(n, sIndex) => n === arr[index + sIndex]
)
);
console.log(index);
You could add a check beforehand to not have to go in every... not sure what that would improve.
const index = largeArray.findIndex(
(item, index, arr) =>
item === smallArray[0] &&
smallArray.every(
(n, sIndex) => n === arr[index + sIndex]
)
);
Other approach is using strings
const largeArray = [1, 2, 3, 8, 2, 3, 4, 5, 6, 7, 8];
const smallArray = [3, 4, 5, 6];
const largeStr = largeArray.join(",");
const smallStr = smallArray.join(",");
const strIndex = largeStr.indexOf(smallStr);
const index = strIndex > -1 ? largeStr.substr(0,strIndex-1).split(",").length : -1;
console.log(index)
To figure out what is better is really based on your use case.
You can use .join to convert the arrays to strings, and use .indexOf to get the index given that you will remove the additional commas:
const getIndexOfSubArray = (arr=[], sub=[]) => {
const str = arr.join();
const subStr = sub.join();
const index = str.indexOf(subStr);
return index < 0 ? -1 : str.substr(0, index-1).split(',').length;
}
console.log( getIndexOfSubArray([1,2,3,8,2,3,4,5,6,7,8], [3,4,5,6]) );
You could iterate by hand and check the items with indexOf.
function getIndex(array, subarray) {
let p = -1,
first = subarray[0];
while ((p = array.indexOf(first, p + 1)) !== -1) {
let i = p,
complete = true;
for (const s of subarray) {
if (s !== array[i++]) {
complete = false;
break;
}
}
if (complete) return p;
}
return -1;
}
console.log(getIndex([1, 2, 3, 8, 2, 3, 4, 5, 6, 7, 8], [3, 4, 5, 6])); // 5
Here is a simple approach to this problem:
let largeArray = [1, 2, 3, 8, 2, 3, 4, 5, 6, 7, 8];
let smallArray = [3, 4, 5, 6];
let s = 0,
i = 0,
j = 0;
let SLen = smallArray.length,
LLen = largeArray.length;
while (i < LLen && j < SLen && SLen - j <= LLen - i) {
if (j == 0) {
s = i;
}
if (largeArray[i] == smallArray[j]) {
j++;
} else {
j = 0;
i = s;
}
i++;
}
let index = i - j;
if (j == SLen) {
console.log(`found at index ${index}`);
} else {
console.log('not found');
}

Reduce over a single array summing slices

I have an array of values
let a = [1,2,3,4,5,6];
I want to sum specific slices, for example a.[0] + a.[1] giving a new array:
[1 + 2, 3 + 4, 5 + 6]
Is there a recommended way to do this with reduce() or other method? Such as some kind of stepping/range parameter?
Because I want #T.J. Crowder to be right :)
const a = [1, 2, 3, 4, 5, 6];
// Loop over all values of the array
const res = a.reduce((tmp, x, xi) => {
// Use Math.floor and xi (the index of the value we are treating)
// to store the values on the returned array at the correct position
tmp[Math.floor(xi / 2)] = (tmp[Math.floor(xi / 2)] || 0) + x;
return tmp;
}, []);
console.log(res);
Will also work if the number of element is not pair
const a = [1, 2, 3, 4, 5];
const res = a.reduce((tmp, x, xi) => {
tmp[Math.floor(xi / 2)] = (tmp[Math.floor(xi / 2)] || 0) + x;
return tmp;
}, []);
console.log(res);
Alternative solution :
const a = [1, 2, 3, 4, 5, 6];
const res = [];
do {
res.push(a.splice(0, 2).reduce((tmp, x) => tmp +x, 0));
} while (a.length);
console.log(res);
You can do this with reduce, but it's not the right tool for the job. Here's how, keying off index and passing an array around:
let array = [1,2,3,4,5,6];
let result = array.reduce((a, v, i) => {
if (i % 2 == 1) {
// It's an odd entry, so sum it with the
// previous entry and push to the result array
a.push(v + array[i - 1]);
}
return a;
}, []);
console.log(result);
You can squash that into a concise arrow function, at the expense of clarity:
let array = [1,2,3,4,5,6];
let result = array.reduce((a, v, i) => ((i % 2 === 1 ? a.push(v + array[i - 1]) : 0), a), []);
console.log(result);
A simple for loop would probably be more appropriate, though:
let array = [1,2,3,4,5,6];
let result = [];
for (let n = 0; n < array.length; n += 2) {
result.push(array[n] + array[n + 1]);
}
console.log(result);
Another approach with Array#flatMap and taking only the odd indices for a value.
var array = [1, 2, 3, 4, 5, 6],
result = array.flatMap((v, i, { [i + 1]: w = 0 }) => i % 2 ? [] : v + w);
console.log(result);
A simple and quick solution with [Array.prototype.reduce] can look like this:
const array = [1,2,3,4,5,6];
const range = 2;
const result = array.reduce((all, item, i) => {
const idx = Math.floor(i/range);
if (!all[idx]) all[idx] = 0;
all[idx] += item;
return all;
},[]);
console.log(result);

Compounding values when mapping array?

I would like to compound values while mapping an array, I tried this but it didn't work:
var array = children.map((child, i) => {
return child.offsetHeight + array[i-1]
})
I would like an array that looks like this:
[1, 5, 3, 2]
to output:
[1, 6, 9, 11]
Using map is not a requirement. But I don't mind using something more intended than a for-loop.
Here an alternative way to other proposals and simple one-liner by using a forEach-loop:
let a = [1, 5, 3, 2],
b = [];
a.forEach((el, it) => { b.push(el + (b[it - 1] || 0)) });
console.log(b)
(b[it - 1] || 0) covers the first iteration where we would access b[-1]
You can use a combination of Array#map, Array#slice and Array#reduce :
.map( ... ) goes through your array
.slice( ... ) cuts a part from your array, from beginning to i+1
.reduce( ... ) returns the sum of the previously cut array
let children = [1, 5, 3, 2];
var array = children.map((child, i) =>
children.slice(0,i+1).reduce((acc, curr) => acc + curr, 0));
console.log(array);
This is one way:
const input = [1, 5, 3, 2];
const result = input.reduce((arr, x, i) =>
i == 0 ? [x] : [...arr, x + arr[arr.length - 1]]
, null)
console.log(result);
Reduce is better than map here, as you get access to the current state, rather than just the current item or the input array.
You can use array#reduce.
var result = [1, 5, 3, 2].reduce((r,v,i) => {
i ? r.push(r[i-1] + v) : r.push(v);
return r;
},[]);
console.log(result);
The easiest solution would be a combination of map slice and reduce:
arr = [1,5,3,2]
result = arr.map((elem, index) => arr.slice(0, index + 1).reduce((a,c) => a+c))
console.log(result)
You can do something like this, you must check at position 0 that array doesn't exist. This solution avoids using reduce and slice each step, improving performance;
var children = [1, 5, 3, 2]
var sum = 0;
var array = children.map((child, i, array) => {
sum = sum + child;
return sum;
})
console.log(array)
Example using for...of:
var arr = [1, 5, 3, 2]
var res = []
var c = 0
for (let item of arr) {
c += item
res.push(c)
}
console.log(res)
//[1, 6, 9, 11]
You could do this with reduce() method instead of map(). So if current index is not 0 you can take last element from accumulator and add current element.
const data = [1, 5, 3, 2]
const result = data.reduce((r, e, i) => {
r.push(i ? +r.slice(-1) + e : e)
return r;
}, []);
console.log(result)
You could also do this with just map() method using thisArg parameter and storing last value inside.
const data = [1, 5, 3, 2]
const result = data.map(function(e) {
return this.n += e
}, {n: 0});
console.log(result)
Or you could just create closure with IIFE and inside use map() method.
const data = [1, 5, 3, 2]
const result = (s => data.map(e => s += e))(0)
console.log(result)

Counting the occurrences / frequency of array elements

In Javascript, I'm trying to take an initial array of number values and count the elements inside it. Ideally, the result would be two new arrays, the first specifying each unique element, and the second containing the number of times each element occurs. However, I'm open to suggestions on the format of the output.
For example, if the initial array was:
5, 5, 5, 2, 2, 2, 2, 2, 9, 4
Then two new arrays would be created. The first would contain the name of each unique element:
5, 2, 9, 4
The second would contain the number of times that element occurred in the initial array:
3, 5, 1, 1
Because the number 5 occurs three times in the initial array, the number 2 occurs five times and 9 and 4 both appear once.
I've searched a lot for a solution, but nothing seems to work, and everything I've tried myself has wound up being ridiculously complex. Any help would be appreciated!
Thanks :)
You can use an object to hold the results:
const arr = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
const counts = {};
for (const num of arr) {
counts[num] = counts[num] ? counts[num] + 1 : 1;
}
console.log(counts);
console.log(counts[5], counts[2], counts[9], counts[4]);
So, now your counts object can tell you what the count is for a particular number:
console.log(counts[5]); // logs '3'
If you want to get an array of members, just use the keys() functions
keys(counts); // returns ["5", "2", "9", "4"]
const occurrences = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4].reduce(function (acc, curr) {
return acc[curr] ? ++acc[curr] : acc[curr] = 1, acc
}, {});
console.log(occurrences) // => {2: 5, 4: 1, 5: 3, 9: 1}
const arr = [2, 2, 5, 2, 2, 2, 4, 5, 5, 9];
function foo (array) {
let a = [],
b = [],
arr = [...array], // clone array so we don't change the original when using .sort()
prev;
arr.sort();
for (let element of arr) {
if (element !== prev) {
a.push(element);
b.push(1);
}
else ++b[b.length - 1];
prev = element;
}
return [a, b];
}
const result = foo(arr);
console.log('[' + result[0] + ']','[' + result[1] + ']')
console.log(arr)
One line ES6 solution. So many answers using object as a map but I can't see anyone using an actual Map
const map = arr.reduce((acc, e) => acc.set(e, (acc.get(e) || 0) + 1), new Map());
Use map.keys() to get unique elements
Use map.values() to get the occurrences
Use map.entries() to get the pairs [element, frequency]
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()])
If using underscore or lodash, this is the simplest thing to do:
_.countBy(array);
Such that:
_.countBy([5, 5, 5, 2, 2, 2, 2, 2, 9, 4])
=> Object {2: 5, 4: 1, 5: 3, 9: 1}
As pointed out by others, you can then execute the _.keys() and _.values() functions on the result to get just the unique numbers, and their occurrences, respectively. But in my experience, the original object is much easier to deal with.
Don't use two arrays for the result, use an object:
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]];
}
Then result will look like:
{
2: 5,
4: 1,
5: 3,
9: 1
}
How about an ECMAScript2015 option.
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
This example passes the input array to the Set constructor creating a collection of unique values. The spread syntax then expands these values into a new array so we can call map and translate this into a two-dimensional array of [value, count] pairs - i.e. the following structure:
Array [
[5, 3],
[2, 5],
[9, 1],
[4, 1]
]
The new array is then passed to the Map constructor resulting in an iterable object:
Map {
5 => 3,
2 => 5,
9 => 1,
4 => 1
}
The great thing about a Map object is that it preserves data-types - that is to say aCount.get(5) will return 3 but aCount.get("5") will return undefined. It also allows for any value / type to act as a key meaning this solution will also work with an array of objects.
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));
I think this is the simplest way how to count occurrences with same value in array.
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))
2021's version
The more elegant way is using Logical nullish assignment (x ??= y) combined with Array#reduce() with O(n) time complexity.
The main idea is still using Array#reduce() to aggregate with output as object to get the highest performance (both time and space complexity) in terms of searching & construct bunches of intermediate arrays like other answers.
const arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
const result = arr.reduce((acc, curr) => {
acc[curr] ??= {[curr]: 0};
acc[curr][curr]++;
return acc;
}, {});
console.log(Object.values(result));
Clean & Refactor code
Using Comma operator (,) syntax.
The comma operator (,) evaluates each of its operands (from left to
right) and returns the value of the last operand.
const arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
const result = arr.reduce((acc, curr) => (acc[curr] = (acc[curr] || 0) + 1, acc), {});
console.log(result);
Output
{
"2": 5,
"4": 1,
"5": 3,
"9": 1
}
If you favour a single liner.
arr.reduce(function(countMap, word) {countMap[word] = ++countMap[word] || 1;return countMap}, {});
Edit (6/12/2015):
The Explanation from the inside out.
countMap is a map that maps a word with its frequency, which we can see the anonymous function. What reduce does is apply the function with arguments as all the array elements and countMap being passed as the return value of the last function call. The last parameter ({}) is the default value of countMap for the first function call.
ES6 version should be much simplifier (another one line solution)
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 }
A Map instead of plain Object helping us to distinguish different type of elements, or else all counting are base on strings
Edit 2020: this is a pretty old answer (nine years). Extending the native prototype will always generate discussion. Although I think the programmer is free to choose her own programming style, here's a (more modern) approach to the problem without extending 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(arrFrequencies);
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; }
The old (2011) answer: you could extend Array.prototype, like this:
{
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();
console.log(`freqs[2]: ${freqs[2]}`); //=> 5
// or
var freqs = '1,1,2,one,one,2,2,22,three,four,five,three,three,five'
.split(',')
.frequencies();
console.log(`freqs.three: ${freqs.three}`); //=> 3
// Alternatively you can utilize 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;
}
}
.as-console-wrapper { top: 0; max-height: 100% !important; }
Based on answer of #adamse and #pmandell (which I upvote), in ES6 you can do it in one line:
2017 edit: I use || to reduce code size and make it more readable.
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},{})
));
It can be used to count characters:
var s="ABRACADABRA";
alert(JSON.stringify(
s.split('').reduce((a, c)=>{a[c]++?0:a[c]=1;return a},{})
));
A shorter version using reduce and tilde (~) operator.
const data = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
function freq(nums) {
return nums.reduce((acc, curr) => {
acc[curr] = -~acc[curr];
return acc;
}, {});
}
console.log(freq(data));
If you are using underscore you can go the functional route
a = ['foo', 'foo', 'bar'];
var results = _.reduce(a,function(counts,key){ counts[key]++; return counts },
_.object( _.map( _.uniq(a), function(key) { return [key, 0] })))
so your first array is
_.keys(results)
and the second array is
_.values(results)
most of this will default to native javascript functions if they are available
demo : http://jsfiddle.net/dAaUU/
So here's how I'd do it with some of the newest javascript features:
First, reduce the array to a Map of the counts:
let countMap = array.reduce(
(map, value) => {map.set(value, (map.get(value) || 0) + 1); return map},
new Map()
)
By using a Map, your starting array can contain any type of object, and the counts will be correct. Without a Map, some types of objects will give you strange counts.
See the Map docs for more info on the differences.
This could also be done with an object if all your values are symbols, numbers, or strings:
let countObject = array.reduce(
(map, value) => { map[value] = (map[value] || 0) + 1; return map },
{}
)
Or slightly fancier in a functional way without mutation, using destructuring and object spread syntax:
let countObject = array.reduce(
(value, {[value]: count = 0, ...rest}) => ({ [value]: count + 1, ...rest }),
{}
)
At this point, you can use the Map or object for your counts (and the map is directly iterable, unlike an object), or convert it to two arrays.
For the Map:
countMap.forEach((count, value) => console.log(`value: ${value}, count: ${count}`)
let values = countMap.keys()
let counts = countMap.values()
Or for the object:
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)
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};
If you still want two arrays, then you could use answer like this...
var uniqueNums = Object.keys(answer);
// uniqueNums => ["2", "4", "5", "9"];
var countOfNums = Object.keys(answer).map(key => answer[key]);
// countOfNums => [5, 1, 3, 1];
Or if you want uniqueNums to be numbers
var uniqueNums = Object.keys(answer).map(key => +key);
// uniqueNums => [2, 4, 5, 9];
Here's just something light and easy for the eyes...
function count(a,i){
var result = 0;
for(var o in a)
if(a[o] == i)
result++;
return result;
}
Edit: And since you want all the occurences...
function count(a){
var result = {};
for(var i in a){
if(result[a[i]] == undefined) result[a[i]] = 0;
result[a[i]]++;
}
return result;
}
Solution using a map with O(n) time complexity.
var arr = [2, 2, 2, 2, 2, 4, 5, 5, 5, 9];
const countOccurrences = (arr) => {
const map = {};
for ( var i = 0; i < arr.length; i++ ) {
map[arr[i]] = ~~map[arr[i]] + 1;
}
return map;
}
Demo: http://jsfiddle.net/simevidas/bnACW/
There is a much better and easy way that we can do this using ramda.js.
Code sample here
const ary = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
R.countBy(r=> r)(ary)
countBy documentation is at documentation
I know this question is old but I realized there are too few solutions where you get the count array as asked with a minimal code so here is mine
// The initial array we want to count occurences
var initial = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
// The count array asked for
var count = Array.from(new Set(initial)).map(val => initial.filter(v => v === val).length);
// Outputs [ 3, 5, 1, 1 ]
Beside you can get the set from that initial array with
var set = Array.from(new Set(initial));
//set = [5, 2, 9, 4]
My solution with 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)
Link to REPL.
Using MAP you can have 2 arrays in the output: One containing the occurrences & the other one is containing the number of occurrences.
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)
This question is more than 8 years old and many, many answers do not really take ES6 and its numerous advantages into account.
Perhaps is even more important to think about the consequences of our code for garbage collection/memory management whenever we create additional arrays, make double or triple copies of arrays or even convert arrays into objects. These are trivial observations for small applications but if scale is a long term objective then think about these, thoroughly.
If you just need a "counter" for specific data types and the starting point is an array (I assume you want therefore an ordered list and take advantage of the many properties and methods arrays offer), you can just simply iterate through array1 and populate array2 with the values and number of occurrences for these values found in array1.
As simple as that.
Example of simple class SimpleCounter (ES6) for Object Oriented Programming and Object Oriented Design
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;
Using Lodash
const values = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4];
const frequency = _.map(_.groupBy(values), val => ({ value: val[0], frequency: val.length }));
console.log(frequency);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
To return an array which is then sortable:
let array = [5, 5, 5, 2, 2, 2, 2, 2, 9, 4]
let reducedArray = array.reduce( (acc, curr, _, arr) => {
if (acc.length == 0) acc.push({item: curr, count: 1})
else if (acc.findIndex(f => f.item === curr ) === -1) acc.push({item: curr, count: 1})
else ++acc[acc.findIndex(f => f.item === curr)].count
return acc
}, []);
console.log(reducedArray.sort((a,b) => b.count - a.count ))
/*
Output:
[
{
"item": 2,
"count": 5
},
{
"item": 5,
"count": 3
},
{
"item": 9,
"count": 1
},
{
"item": 4,
"count": 1
}
]
*/
Check out the code below.
<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>
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]};
}
}, {})
}
You can simplify this a bit by extending your arrays with a count function. It works similar to Ruby’s Array#count, if you’re familiar with it.
Array.prototype.count = function(obj){
var count = this.length;
if(typeof(obj) !== "undefined"){
var array = this.slice(0), count = 0; // clone array and reset count
for(i = 0; i < array.length; i++){
if(array[i] == obj){ count++ }
}
}
return count;
}
Usage:
let array = ['a', 'b', 'd', 'a', 'c'];
array.count('a'); // => 2
array.count('b'); // => 1
array.count('e'); // => 0
array.count(); // => 5
Gist
Edit
You can then get your first array, with each occurred item, using Array#filter:
let occurred = [];
array.filter(function(item) {
if (!occurred.includes(item)) {
occurred.push(item);
return true;
}
}); // => ["a", "b", "d", "c"]
And your second array, with the number of occurrences, using Array#count into Array#map:
occurred.map(array.count.bind(array)); // => [2, 1, 1, 1]
Alternatively, if order is irrelevant, you can just return it as a key-value pair:
let occurrences = {}
occurred.forEach(function(item) { occurrences[item] = array.count(item) });
occurences; // => {2: 5, 4: 1, 5: 3, 9: 1}

Categories

Resources