Convert an Array of 3 numbers to associated letters - javascript

Im learning JS and have set myself the challenge to re-create a football league table, from an array of match results.
Everything is going really well and I'm almost complete, but I can't filter my 'FORM GUIDE' array to their corresponding Letters.
I have the points scored over the last 5 games ONLY outputted as an array (below);
[3, 1, 3, 0, 3]
But I'd like to output this as 3 = W, 1 = D, 0 = L.
So...
W, D, W, L, W
Can someone please explain how I can do this?
Thank you
'use strict';
// Tottenham's Premier League scores & points 21-22 //
//--------------------------------------------------//
const scoresPoints = [
[1, 0, 3], // Watford
[1, 1, 1], // Southampton
[3, 0, 3], // Crystal Palace
[2, 2, 1], // Liverpool
[3, 0, 3], // Norwich
[2, 0, 3], // Brentford
[2, 1, 3], // Leeds
[0, 0, 1], // Everton
[3, 2, 3], // Newcastle
[0, 3, 0], // Man U
[0, 1, 0], // West Ham
[2, 1, 3], // Villa
[1, 3, 0], // Arsenal
[0, 3, 0], // Chelsea
[0, 3, 0], // Crystal Palace
[1, 0, 3], // Watford
[1, 0, 3], // Wolves
[1, 0, 3], // Man City
];
// Define The functions & arrays--------------------//
//--------------------------------------------------//
let tottenhamScores;
let totalTottenhamGoals;
let totalTottenhamPoints;
let totalOpponentsGoals;
let tottenhamForm = [];
// The goals scored by Tottenham
const tottenhamGoals = [];
// The points scored by Tottenham
const tottenhamPoints = [];
// The goals scored by the opponents
const opponentsGoals = [];
// Filter the data from each array------------------//
//--------------------------------------------------//
for (let i = 0; i < scoresPoints.length; i++) {
tottenhamScores = scoresPoints[i][0];
// Just Tottenham's goals
tottenhamGoals.push(tottenhamScores);
// Just Tottenham's points
const leaguePoints = scoresPoints[i][2];
tottenhamPoints.push(leaguePoints);
// Just the opponents goals
const opponentsScores = scoresPoints[i][1];
opponentsGoals.push(opponentsScores);
// Just Tottenham's Form
const leagueForm = scoresPoints[i][2];
tottenhamForm.push(leagueForm);
}
// Adding up the arrays-----------------------------//
//--------------------------------------------------//
// Adding up Tottenham's goals
for (let i = 0; i < tottenhamGoals.length; i++) {
totalTottenhamGoals = tottenhamGoals.reduce(function (a, b) {
return a + b;
}, 0);
}
// Adding up Tottenham's points
for (let i = 0; i < tottenhamPoints.length; i++) {
totalTottenhamPoints = tottenhamPoints.reduce(function (a, b) {
return a + b;
}, 0);
}
// Adding up the opponents goals
for (let i = 0; i < opponentsGoals.length; i++) {
totalOpponentsGoals = opponentsGoals.reduce(function (a, b) {
return a + b;
}, 0);
}
// Last 5 games-------------------------------------//
//--------------------------------------------------//
// Find the individual values
function occurrence(pointValues, value) {
return pointValues.filter(v => v === value).length;
}
const win = occurrence(tottenhamPoints, 3);
const draw = occurrence(tottenhamPoints, 1);
const loss = occurrence(tottenhamPoints, 0);
// Filter to last five games
function lastFiveGames(form, five) {
form = tottenhamForm.slice(0, five);
return form;
}
const latestForm = lastFiveGames(tottenhamForm, 5);
// Convert points to represented letters
const letteredResult = latestForm.map(result => {
switch (result) {
case 0:
return 'L';
case 1:
return 'D';
case 3:
return 'W';
default:
return 'No Form To Display';
}
});
// Print the statement & table----------------------//
//--------------------------------------------------//
console.log(
`SUMMARY
--------
--------
Throughout the 2021-22 Premier League season, Tottenham Hotspur have scorred ${totalTottenhamGoals} goals and conceeded ${totalOpponentsGoals}.
This has gained Tottenham Hotspur ${totalTottenhamPoints} points to date.
(Dropping ${
scoresPoints.length * 3 - totalTottenhamPoints
} points throughout the season from the maximum of ${
scoresPoints.length * 3
} available).
TABLE & FORM GUIDE ---
----------------------
${scoresPoints.length} Played
${win} Wins
${draw} Draws
${loss} Losses
${totalTottenhamGoals} For
${totalOpponentsGoals} Against
${totalTottenhamGoals - totalOpponentsGoals} Goal Difference
${totalTottenhamPoints} POINTS IN TOTAL
FORM (Last 5 Games) ${letteredResult}
----------------------
----------------------`
);

You can achieve this with Array.map and a nice switch statement. Try this way:
const arr = [3, 1, 3, 0, 3]
const matched = arr.map(result => {
switch(result){
case 0:
return 'L'
case 1:
return 'D'
case 3:
return 'W'
default:
return 'X'
}
})
console.log(matched)

You can use map:
[3, 1, 3, 0, 3].map(e => { if (e == 3) return "W"; if (e == 1) return "D"; if (e == 0) return "L"; throw `Unexpected value: ${e}`; })

let arr = [3, 1, 3, 0, 3];
let arrMap = arr.map((i) => {
if (i == 3) return "W";
if (i == 0) return "L";
return "D"
})
console.log(arrMap)

You can achieve this clean and simple using map and an object acting as Enum where the key will represent the points and the value will represent the result char.
const array = [3, 1, 3, 0, 3];
const cases = {0: 'L', 1: 'D', 3: 'W'};
const matches = array.map(e => cases[e]);
console.log(matches)
More about map - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map

Instead of using map, you could also use forEach. The main difference is that map will return a new array, whereas forEach doesn't return anything and can be used to change the original array. Here is how:
const logic = {
3: 'W',
1: 'D',
0: 'L'
}
const data = [3, 1, 3, 0, 3];
data.forEach((value, key) => data[key] = logic[value]);
console.log(data);

const arr = [3, 1, 3, 0, 3, 2, 4];
const data = arr.map(v => ['L','D','_','W'][v] || '_');
console.log( data );

Related

I need my functions the stop running and break once a condition is meet, without an error popping up

I am trying to make a sudoku solver. the code works, However when there are no more 0(zeros) in the table that means my sudoku table is solved. if the sudoku table has been solve I want everything to stop running. The problem is I have not found a good way to stop program from running when it is solved. I am open to suggestions.
the case that is supposed to stop the program from running is function find_empty_space(table), it is the first if condition, however in the function sudoku_solver(table) it calls var values = find_empty_space(table); and since I don't return anything if this case occurs an error happens.
I tried adding a if condition below var values = find_empty_space(table); where values == null then nothing happens so the function can stop but that for some reason ruins the sudoku solver from working. if anyone has another idea how to stop my program and all other functions after condition is meet please lmk
var sudoku_1 = [
[0, 0, 0, 0],
[1, 0, 2, 0],
[0, 1, 4, 0],
[2, 0, 0, 1],
];
var table = sudoku_1;
function find_empty_space(table) {
if (
table[0].indexOf(0) == -1 &&
table[1].indexOf(0) == -1 &&
table[2].indexOf(0) == -1 &&
table[3].indexOf(0) == -1
) {
console.log("Sudoku Solver has solved your table");
var solved_table = table;
} else {
for (var r = 0; r < 5; r++) {
for (var c = 0; c < 5; c++) {
console.log(`row: ${r}`); //!for testing
console.log(`column: ${c}`); //!for testing
// if object in array is equal to 0 then it means the space is empty
if (table[r][c] == 0) {
return [r, c];
}
}
}
}
}
function check_if_number_can_go_in_position(table, n, r, c) {
console.log("function check_if_number_can_go_in_position()");
console.log(`row ${table[r]}`);
// var below makes a array of tables column that is need to search for n
var column_c = table.map((d) => d[c]);
console.log(`col ${column_c}`);
if (table[r].indexOf(n) != -1) {
console.log("backtrack r");
return false;
}
if (column_c.indexOf(n) != -1) {
console.log("backtrack c");
return false;
}
return true;
}
// this is the main function
function sudoku_solver(table) {
var values = find_empty_space(table);
console.log(values);
var r = values[0];
var c = values[1];
console.log("in one");
for (var n = 1; n < 5; n++) {
console.log(`n = ${n}`);
if (check_if_number_can_go_in_position(table, n, r, c) == true) {
table[r][c] = n;
console.table(table);
sudoku_solver(table);
}
}
table[r][c] = 0;
}
sudoku_solver(table);
Assuming that this is for the 16x16 Sudoku variant since the input in the OP is only 4x4 -- going along with this assumption then you need to find all of the blanks (represented as a 0) in a box. In the following example, function blankMap(box) takes a 2D array of any size and returns an array of pairs. An array of pairs is a 2D array of N rows and 2 colunms. In each pair (or sub-array) is the location of a 0 -- the first colunm (Array[N][0]) represents the index number of a row, the second colunm (Array[N][1]) represents the index number of a column.
INPUT
OUTPUT
const box9 = [
const box9X = [
[0, 12, 0, 9],
[0, 0], [0, 2],
[16, 3, 0, 8],
[1, 2],
[0, 0, 0, 0],
[2, 0], [2, 1], [2, 2], [2, 3],
[10, 5, 2, 0]
[3, 3]
];
];
/* INPUT
A 4 row by 4 colunm table represents 1 of a total of 16 sub-tables.
*/
const box0 = [
[0, 5, 13, 0],
[10, 0, 0, 0],
[7, 11, 3, 0],
[0, 0, 0, 0]
];
const box13 = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 16]
];
const blankMap = box =>
box.flatMap((arr, row) => // 1st grab each sub-array (~arr~)
arr.flatMap((num, col) => // On each ~arr~,...
num === 0 ? [[row, col]] : [])); /* ...if it's a ~0~ ~?~ then return the position in double
brackets: ~[[row, col]]~ otherwise ~:~ return an empty array ~[]~.*/
// A utility to verify if a box is completed
const isDone = array => array.length < 1;
console.log('box0=-=-=-=-=-=-=-=-=-=-=-=-=');
let b0 = blankMap(box0);
console.log(JSON.stringify(b0));
console.log('box0 is complete: '+isDone(b0));
console.log('box13-=-=-=-=-=-=-=-=-=-=-=-=');
let b13 = blankMap(box13);
console.log(JSON.stringify(b13));
console.log('box13 is complete: '+isDone(b13));
The .flatMap() method was used twice: once to iterate through the outer array and grab the sub-arrays, and once to iterate through each sub-array to find the 0s. .flatMap() is the .map() and .flat() methods combined so if you want the return as a 2D array, wrap it in brackets (double if another .flatMap() is working on the same array). Inversely, if you want to totally ignore an iteration then return an empty array [].
Marked the added code
function find_empty_space(table) {
// function goes from right to left of table finding every empty space, empty space == 0,
//for loops go through 1-4
// var meanings r = row, c = column
if (
table[0].indexOf(0) == -1 &&
table[1].indexOf(0) == -1 &&
table[2].indexOf(0) == -1 &&
table[3].indexOf(0) == -1
) {
console.log("Sudoku Solver has solved your table");
var solved_table = table;
return true; // ***** added this
} else {
for (var r = 0; r < 5; r++) {
for (var c = 0; c < 5; c++) {
console.log(`row: ${r}`); //!for testing
console.log(`column: ${c}`); //!for testing
// if object in array is equal to 0 then it means the space is empty
if (table[r][c] == 0) {
return [r, c];
}
}
}
}
}
// this is the main function
function sudoku_solver(table) {
// contains all other sub functions this is the main function
//r = row, c = column
// function goes from right to left of table finding every empty space, empty
var values = find_empty_space(table);
if (values === true) return true; // ***** added this
console.log(values);
var r = values[0];
var c = values[1];
console.log("in one");
for (var n = 1; n < 5; n++) {
console.log(`n = ${n}`);
if (check_if_number_can_go_in_position(table, n, r, c) == true) {
table[r][c] = n;
console.table(table);
if (sudoku_solver(table) === true) // ***** changed this
return true; // ***** added this
}
}
table[r][c] = 0;
}
sudoku_solver(table);

converting isSubMatrixFull function from Python to JS

I am trying to convert(as close to original(Python in this case)) as possible the following function from Python to JS.
def getCol(mat, col):
return [mat[i][col] for i in range(3)]
def isSubMatrixFull(mat):
n = len(mat[0])
ans = [False]*(n-2)
kernel = getCol(mat, 0) + getCol(mat, 1) + getCol(mat, 2) # O(1)
for i in range(n - 2): # O(n)
if len(set(kernel)) == 9: # O(1)
ans[i] = True # O(1)
if i < n - 3: # O(1)
kernel = kernel[3:] + getCol(mat, i + 3) # O(1)
return ans
nums = [[1, 2, 3, 2, 5, 7], [4, 5, 6, 1, 7, 6], [7, 8, 9, 4, 8, 3]]
print(isSubMatrixFull(nums))
So far, I have come to this:
function isSubMatrixFull(mat) {
let test = new Array();
function getCol(mat, col) {
for (let i in mat) {
test.push([mat[i][col]]);
// I have to find a way, to basically assign test values to [mat[i][col]]
// and then send it back to getCol(mat, i + 3)
}
}
getCol(mat, 3);
// The below would be unnecessary if I would have managed to assign test values(or
// would have found other way) to [mat[i][col]] and send back to getCol(mat, i + 3)
test = test
.map((v) => v)
.join("")
.split("")
.map(Number);
let n = mat[0].length;
let res = new Array(n - 2).fill(false);
let main = mat
.map((rowValue, rowIndex) => mat.map((val) => val[rowIndex]))
.join(",")
.split(",");
for (let i = 0; i < n - 2; i++) {
if (new Set(main).size == 9) {
res[i] = true;
}
if (i < n - 3) {
main = main.slice(3) + getCol(mat, i + 3);
}
}
// return res;
}
console.log(
isSubMatrixFull([
[1, 2, 3, 2, 5, 7],
[4, 5, 6, 1, 7, 6],
[7, 8, 9, 4, 8, 3],
])
);
The **output** of the above functions' input should be
isSubmatrixFull(numbers) = [true, false, true, false]
Please see here for more info about this question: https://leetcode.com/discuss/interview-question/860490/codesignal-intern-issubmatrixfull
Please read the comments(inside code) to see what I have done and trying to do.
This code is a direct port from the python. It works for the example provided.
const getColumn = (matrix, column) => {
return [...Array(3).keys()].map((i) => {
return matrix[i][column];
});
};
const computeKernel = (matrix) => {
return [
...getColumn(matrix, 0),
...getColumn(matrix, 1),
...getColumn(matrix, 2),
];
};
const isSubMatrixFull = (matrix) => {
const n = matrix[0].length;
const answer = [...Array(n - 2)].map((_) => {
return false;
});
let kernel = computeKernel(matrix);
[...Array(n - 2).keys()].forEach((i) => {
if (new Set(kernel).size === 9) {
answer[i] = true;
}
if (i < n - 3) {
kernel = [...kernel.slice(3), ...getColumn(matrix, i + 3)];
}
});
return answer;
};
const numbers = [
[1, 2, 3, 2, 5, 7],
[4, 5, 6, 1, 7, 6],
[7, 8, 9, 4, 8, 3],
];
console.log(isSubMatrixFull(numbers));

maximize the groups of combinations to use all values

Given an array of combinations (to a certain sum), I'm struggling to find pairs of unique combinations that use all the numbers from the source.
function maximizeGroups(groups, source) {}
const groups = [[10], [1, 3, 6], [1, 9], [3, 7], [4, 6]];
const source = [6, 1, 3, 10, 4, 7, 9]
console.log(maximizeGroups(groups, source))
-> expected result [[6, 4], [7, 3], [10], [9, 1]]
Here [1, 3, 6] is discarded.
Since use of this combination doesn't allow to use the number 4
Function used:
const groups = [[10], [1, 3, 6], [1, 9], [3, 7], [4, 6]];
const source = [6, 1, 3, 10, 4, 7, 9];
function maximizeGroups(groups, source) {
const sorted = source
.slice()
.sort((a, b) => a - b)
.join('');
for (let i = 0; i < groups.length - 1; i++) {
const copy = groups.slice();
copy.splice(i, 1);
const check = copy
.slice()
.flat()
.sort((a, b) => a - b)
.join('');
if (check === sorted) {
return copy;
}
}
return null;
}
Here is what should be a more efficient solution. Basically as I'm piecing together the groupings I'm making sure that I'm only considering groups that could wind up summing to the target.
If you have a large number of groups, and there is no solution, this could fail in exponential time. But normally it will finish fairly quickly.
// Helper functions to extract the parts of a "pos:value" pair.
function pairToPos (pair) {
return Number(pair.split(":")[0]);
}
function pairToValue (pair) {
return Number(pair.split(":")[1]);
}
function sum (nums) {
let total = 0;
nums.forEach( (value) => {total += value} );
return total;
}
function recursiveSolution(nums, target, maxGroups, sumLookup, pos, partialAnswers) {
if (pos == nums.length) {
return partialAnswers;
}
// Try starting a new group.
if ((partialAnswers.length < maxGroups)
&& ([pos, target].join(":") in sumLookup)) {
// Try adding a new group.
partialAnswers.push([nums[pos]]);
let attempt = recursiveSolution(nums, target, maxGroups, sumLookup, pos+1, partialAnswers);
if (attempt != null) {
return partialAnswers;
}
// Get rid of my new group.
partialAnswers.pop();
}
// Try adding this value to each group.
let finalAnswers = null;
partialAnswers.forEach( (group) => {
if (finalAnswers != null) {
// Do nothing, we have the answer.
}
else if ([pos, target - sum(group)].join(":") in sumLookup) {
// Try adding this value.
group.push(nums[pos]);
let attempt = recursiveSolution(nums, target, maxGroups, sumLookup, pos+1, partialAnswers);
if (attempt != null) {
finalAnswers = partialAnswers;
}
else {
// Get rid of my new value.
group.pop();
}
}
});
return finalAnswers;
}
function deriveGroups(nums, target) {
// Use dynamic programming to find all paths to the target.
let sumLookup = {};
// We constantly use "pos:value" pairs as keys.
// This entry means "the empty sum off the array is 0".
sumLookup[[nums.length, 0].join(":")] = null;
// We go backwards here to get the future sum from current value + pos.
for (let i = nums.length-1; 0 <= i; i--) {
let term = nums[i];
Object.keys(sumLookup).forEach( (pair) => {
let prevPos = pairToPos(pair);
let prevValue = pairToValue(pair);
let nextPair = [i, prevValue + term].join(":");
if (! (nextPair in sumLookup)) {
sumLookup[nextPair] = [];
}
sumLookup[nextPair].push(prevPos);
});
}
return recursiveSolution(nums, target, sum(nums)/target, sumLookup, 0, []);
}
console.log(deriveGroups([9, 2, 13, 10, 2, 3], 13));

Finding the mode's of an array? Javascript

Okay, I've only figured out how to get one mode out of the array..
But I want to get 2, 3 or more if they occur the same amount of times.
This is the code:
var frequency = {}; // array of frequency.
var maxFreq = 0; // holds the max frequency.
for (var i in array) {
frequency[array[i]] = (frequency[array[i]] || 0) + 1; // increment frequency.
if (frequency[array[i]] > maxFreq) { // is this frequency > max so far ?
maxFreq = frequency[array[i]]; // update max.
mode = array[i]; // update result.
}
}
So right now, if I've got a array = [3, 8, 3, 6, 1, 2, 9];
I get mode = 3;
But what I'm looking for is if array = [3, 6, 1, 9, 2, 3, 6, 6, 3, 1, -8, 7];
I want to get the mode = 3, 6;
The question doesn't state how to get the modes, but if we want them in an array, we could change the code like this:
function getModes(array) {
var frequency = []; // array of frequency.
var maxFreq = 0; // holds the max frequency.
var modes = [];
for (var i in array) {
frequency[array[i]] = (frequency[array[i]] || 0) + 1; // increment frequency.
if (frequency[array[i]] > maxFreq) { // is this frequency > max so far ?
maxFreq = frequency[array[i]]; // update max.
}
}
for (var k in frequency) {
if (frequency[k] == maxFreq) {
modes.push(k);
}
}
return modes;
}
alert(getModes([3, 6, 1, 9, 2, 3, 6, 6, 3, 1, -8, 7]));
function modeCount(data) {
let modecount = [];
let valueArr = [];
let dataSet = new Set(data);
for (const iterator of dataSet) {
const filteredNum = data.filter((num) => iterator === num);
modecount.push({
mode: iterator,
count: filteredNum.length
});
}
modecount.sort((a, b) => {
return b.count - a.count;
});
modecount.forEach(value => {
if (value.count === modecount[0].count) {
valueArr.push(value.mode);
}
});
return valueArr;
}
let ages = [3, 6, 1, 9, 2, 3, 6, 6, 3, 1, -8, 7]
console.log(modeCount(ages));

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