Related
I am trying to split an array which has a repeating pattern of elements 1, 2, 3, and 4. I want to turn my array [1,2,3,4,5,6,7,8,9,10] into four arrays: [1,5,10], [2,6,11], [3,7,12], and [4,8,13]. I tried using multiples, but the result creates the new arrays in a wrong order. Here is my attempt:
var upload_names_and_ids = [
"Certificat de salaire", //first line is the upload's visible title
"certificat-de-salaire", //second line is the upload's id
"no-info-circle", //third line is the info-circle class
"", //fourth line is the info-circle text
"Allocations Familiales",
"alloc-familiales",
"no-info-circle",
"",
"Courrier Impot (déclaration précédente)",
"courrier-impot",
"info-circle right",
""
];
//Seperate our first array into 4
var upload_names = [];
var upload_ids = [];
var upload_info_circle_class = [];
var upload_info_circle_content = [];
for (var i=0; i<upload_names_and_ids.length; i++){
if (i%4==0) {
upload_info_circle_content.push(upload_names_and_ids[i]);
} else if (i%3==0) {
upload_info_circle_class.push(upload_names_and_ids[i]);
} else if (i%2==0) {
upload_names.push(upload_names_and_ids[i]);
} else {
upload_ids.push(upload_names_and_ids[i]);
}
}
Any help is much appreciated, thank you!
You could take a remainder with index and wanted length.
const
array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16],
length = 4,
result = array.reduce(
(r, v, i) => (r[i % length].push(v), r),
Array.from({ length }, _ => [])
);
console.log(result);
If you like to use predeclared array directly, you could replace this line
Array.from({ length }, _ => [])
with
[upload_names, upload_ids, upload_info_circle_class, upload_info_circle_content]
where the accumulator of Array#reduce keeps the object references.
It's not i%3==0 (which matches 0, 3, 6, …) but i%4==1 (to match 1, 5, 10, …). Same for i%2==0.
I would add a helper sliceN that takes an array and a positive integer. Then returns an array of arrays where the inner arrays are of length n.
sliceN([1,2,3,4,5,6,7,8,9], 3) //=> [[1,2,3], [4,5,6], [7,8,9]]
sliceN([1,2,3,4,5,6], 2) //=> [[1,2], [3,4], [5,6]]
Then also add a helper transpose that transposes a matrix.
transpose([[1,2,3], [4,5,6], [7,8,9]]) //=> [[1,4,7], [2,5,8], [3,6,9]]
transpose([[1,2], [3,4], [5,6]]) //=> [[1,3,5], [2,4,6]]
With these two helpers you can create the wanted result with ease.
const upload_names_and_ids = [
"Certificat de salaire", //first line is the upload's visible title
"certificat-de-salaire", //second line is the upload's id
"no-info-circle", //third line is the info-circle class
"", //fourth line is the info-circle text
"Allocations Familiales",
"alloc-familiales",
"no-info-circle",
"",
"Courrier Impot (déclaration précédente)",
"courrier-impot",
"info-circle right",
""
];
const [
upload_names,
upload_ids,
upload_info_circle_class,
upload_info_circle_content,
] = transpose(sliceN(upload_names_and_ids, 4));
console.log(upload_names);
console.log(upload_ids);
console.log(upload_info_circle_class);
console.log(upload_info_circle_content);
function sliceN(array, n) {
const slices = [];
for (let i = 0; i < array.length; i += n) {
slices.push(array.slice(i, i + n));
}
return slices;
}
function transpose(rows) {
if (rows.length == 0) return [];
const columns = rows[0].map(cell => Array.of(cell));
for (let iRow = 1; iRow < rows.length; iRow += 1) {
for (let iCol = 0; iCol < columns.length; iCol += 1) {
columns[iCol].push(rows[iRow][iCol]);
}
}
return columns;
}
If you are already use a library with helper functions chances are that one or both of these data transforming methods are present. sliceN can often be found as something with split, slice or chunk in the name. transpose is very specific and if present will probably be present under the same name.
As an example Ramda offers both these methods.
R.transpose(R.splitEvery(4, upload_names_and_ids))
Im wondering about a problem I have in Javascript. I have a scenario where I need to fill gaps in an array with averages of the surrounding values. Let me give an example:
Array:
1, 2, 3, ,4, 5
In this particular case I would need to fill the gap with average of the surrounding numbers, i.e. 3.5. I think this is relatively easy to do.
However, I also need to make sure this works when there is more subsequent gaps in the array.
Example:
1, 2, 3, , , 5, 6
In this case the two gaps should be filled with the average of 3 and 5, resulting in ... 3, 4, 4, 5.
The moment I got stuck was when I tried iterating the array and filling the gaps because I filled the first gap with 4, but at that moment, the surrounding numbers for the second gap were 4 (the original gap) and 5, so I ended up with
... 3, 4, 4.5, 5, ...
which is not what I need.
Im using jQuery to iterate the array and get the values from a table.
This is how Im loading the array:
var list = [];
$('#mytable > tbody > tr').each(function() {
$this = $(this);
list.push(eval($this.find("input.number").val());
}
Now I need to iterate and fill the gaps in "list"
Here's one possible implementation:
function fill(arr) {
while (arr.includes(undefined)) {
const startIndex = arr.findIndex(num => num === undefined);
const endIndex = arr.findIndex((num, i) => i >= startIndex && num !== undefined);
const avg = (arr[startIndex - 1] + arr[endIndex]) / 2;
for (let i = startIndex; i < endIndex; i++) arr[i] = avg;
}
return arr;
}
console.log(fill([1, 2, 3, , , 5, 6]));
console.log(fill([1, , , , , , 6]));
console.log(fill([1, , , , 3, 4, , , 6]));
Try this code
var x = "1, 2, 3, ,4, 5";
var y = "1, 2, 3, , , 5, 6";
var z = "1, , , , , , 6";
var q = "1, , , , 3, 4, , , 6";
function removeSpace(str) {
return str.replace(/ /g, "");
}
function splitString(str) {
return str.split(',');
}
function fill(str) {
var z = removeSpace(str);
var zPrime = splitString(z);
for (var i = 0; i < zPrime.length; i++) {
if (zPrime[i] == "") {
if (i + 1 < zPrime.length && zPrime[i + 1] != "") {
zPrime[i] = (Number(zPrime[i - 1]) + Number(zPrime[i + 1])) / 2;
} else {
var j = i + 1;
while (j < zPrime.length && zPrime[j] == "") j++;
var tp = (j < zPrime.length) ? Number(zPrime[j]) : 0;
var dn = (i - 1 > -1) ? Number(zPrime[i - 1]) : 0;
for (var k = i; k < j; k++) {
zPrime[k] = ((tp + dn) / 2) + '';
}
}
}
}
return zPrime;
}
console.log(fill(x));
console.log(fill(y));
console.log(fill(z));
console.log(fill(q));
You can do something like this using simple iteration.
function fillMissing(arr) {
// array for storing result
var res = [];
// iterate over the array
for (i = 0; i < arr.length; i++) {
// if undefined then call the function to calculate average or set the value
res[i] = arr[i] == undefined ? calculateAverage(arr, i) : arr[i];
}
return res;
}
function calculateAverage(arr1, i) {
// set preve and next value as nearest element
var prev = arr1[i - 1],
next = arr1[i + 1],
j = 1; // variable for iterating
// iterate to find out nearest defined value
// after the element
while (prev == undefined) { prev = arr1[i - ++j]; }
j = 1; // reset for next iteration
// iterate to find out nearest defined value
// before the element
while (next == undefined) { next = arr1[i + ++j]; }
//find average and return
return (prev + next) / 2;
}
console.log(fillMissing([1, 2, 3, , , 5, 6]));
console.log(fillMissing([1, 2, 3, , , 5, 6]));
console.log(fillMissing([1, , , , , , 6]));
console.log(fillMissing([1, , , , 3, 4, , , 6]));
You could iterate the sparse array and store the last index and value. If a gap is found, the gap is filled with the average of the last value and the actual value.
function fill(array) {
array.reduce((last, v, i, a) => {
var j, avg;
if (last.index + 1 !== i) {
avg = (v + last.value) / 2;
for (j = 1; j < i - last.index; j++) {
a[last.index + j] = avg;
}
}
return { index: i, value: v };
}, { index: -1 });
return array;
}
console.log(fill([1, 2, 3, , , 5, 6]));
console.log(fill([1, , , , , , 6]));
console.log(fill([1, , , , 3, 4, , , 6]));
Interesting question that made me think a bit.
My first idea was to use map but I found that it does not consider holes. Fixed that with Array.from that converts holes to undefined
I was hoping to find a more concise solution but I'm posting it anyways
function fillMissing(a){
return Array.from(a).map((e,i)=> e !== undefined ? e : averageAround(i,a))
}
const averageAround = (index, array) => average(before(index, array), after(index, array))
const average = (a, b) => (a+b)/2
const before = findFirstNotUndefined.bind(null, -1)
const after = findFirstNotUndefined.bind(null, 1)
function findFirstNotUndefined(direction, index, array){
if (array[index] !== undefined) return array[index]
return findFirstNotUndefined(direction, index+direction, array)
}
console.log(fillMissing([1, 2, 3, , , 5, 6]));
Some thoughts:
recursive call to findFirstNotUndefined is in tail call position
findFirstNotUndefined should be memoizable, (maybe) useful for large holes
average around can be written point free style but not without adding another function or importing some fp library like ramda
Modifying the signature of averageAround like (_, index, array), it can be used directly in the map: Array.from(a).map(averageAround). Cool to read even if it computes the average on every value ((val + val)/2)
There is a recursive way to do this, Basically it counts the empty spots between two values and divides the difference over these empty spots:
var arr = [1, , , , 3, 4, , , 6];
for( var i = 0; i < arr.length; i++ ){
if( arr[ i ] === undefined )
arr[ i ] = avg( arr[ i - 1 ], arr.slice( i + 1, arr.length ), 1 );
}
function avg( low, arr, recursion ){
if( arr[ 0 ] === undefined )
return avg( low, arr.slice( 1, arr.length ), recursion + 1 );
return low + ( ( arr[0] - low ) / ( recursion + 1 ) );
}
console.log( arr );
Works like a charm:
arr = [1, 2, 3, ,4, 5] => [1, 2, 3, 3.5, 4, 5]
arr = [1, 2, 3, , , 5, 6] => [1, 2, 3, 3.6665, 4.3333, 5, 6]
arr = [1, , , , 3, 4, , , 6] => [1, 1.5, 2, 2.5, 3, 4, 4.6667, 5.3334, 6]
Another functional recursive version
const arr = [1,2,3,,,5,6]
function fill([head,...tail], lastNotNull, holeWidth = 0){
if (tail.length === 0 ) return head
if (head === undefined) return fill(tail, lastNotNull, ++holeWidth)
return Array(holeWidth).fill((lastNotNull+head)/2).concat(head).concat(fill(tail, head))
}
console.log(fill(arr))
Explanation:
First parameter of fill function is destructured into head and tail. Head represent the current value and tail next values
Base case: tail empty -> we just return head
If current value head is undefined, we recurse increasing the size of the hole and keeping the last not null value, to compute the average
In all other cases we fill the hole with the average, we add the current value and then recurse onto remaining array, resetting lastNotNull to head and holeWidth to 0 with default value
I feel that the last step could be optimized in terms of execution time but I like the fact that it's compact and clear (if already comfortable with recursion)
Bonus: I love the fact that the hole is filled with the Array.prototype.fill function
// Given an array of integers [2, 1, 2, 101, 4, 95, 3, 250, 4, 1, 2, 2, 7, 98, 123, 99, ...]
I'm trying to Write a function (with linear run-time complexity) to print the following tabular output with ‘xxx' that resembles a histogram (the output should closely match the sample output below, including "99+" to capture the count for all numbers > 99):
Num | count
1 | xx
2 | xxxx
3 | x
4 | xx
98 | x
99 | x
99+| xxx
const dict = {}; // Empty dictionary
var min = Number.MAX_VALUE;
const maxRange = 5; // elements above maxRange will be clubbed in the same range.
//var arr = [2, 1, 2, 101, 4, 95, 3, 250, 4, 1, 2, 2, 7, 98, 123, 99];
const arr = [1, 2, 5, 3, 2, 2, 1, 5, 5, 6, 7, 1, 8, 10, 11, 12];
// iterate the array and set and update the counter in map
arr.forEach(function(num) {
min = Math.min(min, num); // find min
if (num > maxRange) {
num = maxRange + 1;
}
dict[num] = dict[num] ? dict[num] + 1 : 1;
});
console.log("Num | Count");
// Print the occurrences per item in array starting from min to max
while (min <= maxRange + 1) {
if (!dict[min]) { // print only those numbers which are defined in dictionary
min++;
continue;
}
var xArr = []
var range = dict[min];
for (i = 0; i < range; i++) {
xArr.push('x');
}
var disp = (min <= maxRange) ? (min + " | " + xArr.join("")) : (maxRange + "+ | " + xArr.join(""));
console.log(disp);
min = min + 1;
}
You might want to try iterating over the array using the forEach() method. then create an array of "x"'s as long as the current item in the array. Then use the join method to join the array into a string of x's.
I haven't included how to handle numbers over 99 but I think you this should be enough to get you started. The solution would involve using a conditional statement to check if the number is above 99 and printing accordingly.
i got the following output from my example:
Num | Count
2 ' |' 'xx'
4 ' |' 'xxxx'
6 ' |' 'xxxxxx'
8 ' |' 'xxxxxxxx'
have fun!
var arr = [2,4,6,8]
printHistogram = (array) => {
console.log("Num", '|', "Count")
array.forEach((x, i) => { //iterate over the array (x = current item, i = index)
var arrToJoin =[] //create an empty array
for(i = 0; i < x; i++) {
arrToConcat.push('x') //add an "x" to the array
}
console.log(i, ' |', arrToConcat.join(''))
})
}
printHistogram(arr)
How about this?
create an array of all the unique numbers called arrToCompare
then iterate over that array and compare each number to each number in the original array. If they are equal push an x to the array to join. Join it and log it with the appropriate symbols.
var arr = [7, 7, 8, 9, 2,4,6,8,2]
printHistogram = (array) => {
var arrToCompare =[]
console.log("Num", '|', "Count")
array.forEach((x, i) => {
arrToCompare.includes(x) ? "" : arrToCompare.push(x)
})
arrToCompare.forEach(function(x) {
var arrToJoin = []
array.forEach(function(i) {
if(i === x) {
arrToJoin.push('x')
}
})
console.log(x, '|', arrToJoin.join(''))
})
}
printHistogram(arr)
After sorting an array of objects based on of their property values ("rating" in this case), how do you associate ranks for each object if there are ties between some of these values? Here's an example:
//Should be tied for 1st Rank
var obj1 = {
name: "Person1",
rating: 99
}
//Should be 3rd Rank
var obj2 = {
name: "Person2",
rating: 50
}
//Should be 2nd Rank
var obj3 = {
name: "Person3",
rating: 98
}
//Should be 4th Rank
var obj4 = {
name: "Person4",
rating: 0
}
//Should be tied for 1st Rank
var obj5 = {
name: "Person5",
rating: 99
}
Here's as far as I got:
var clients = [obj1, obj2, obj3, obj4, obj5];
var sorted = [];
for (var i = 0; i < clients.length; i++) {
sorted.push(clients[i]);
}
sorted.sort(function(a, b) {
return b.rating-a.rating;
});
Ultimately, I'd like to be able to get the rank using the object name, like this:
alert(sorted.indexOf(obj5) + 1);
Created a solution that worked, albeit ugly. Thanks jamie for some framework used in this:
for (var i = 0; i < clients.length; i++) {
sorted.push(clients[i]);
}
sorted.sort(function(a, b) {
return b.rating-a.rating;
});
for(var i = 0; i < sorted.length; i++) {
// original ranking
sorted[i].rank = i + 1;
}
function sortRanking() {
for (var k = 0; k < sorted.length; k++) {
for (var h = 1; h < sorted.length + 1; h++) {
if (sorted[k+h] !== undefined) {
if (sorted[k+h].tie !== true) {
if (sorted[k].rating === sorted[h + k].rating) {
sorted[k].rank = k + 1;
sorted[h + k].rank = k + 1;
sorted[k].tie = true;
sorted[h + k].tie = true;
}
}
}
}
}
}
sortRanking();
alert("Rank: " + obj3.rank);
Using ES6, here's how you can do it, adding a property rank to every client. Try the code snippet below.
function setRanks(clients) {
let currentCount = -1, currentRank = 0,
stack = 1; // consecutive clients with same rating
for (let i = 0; i < clients.length; i++) {
const result = clients[i];
if (currentCount !== result['rating']) {
currentRank += stack;
stack = 1;
} else {
stack++;
}
result['rank'] = currentRank;
currentCount = result['rating'];
}
}
// get the rank using the object name
function getRank(clientName) {
return clients.find(c => c.name === clientName)['rank'];
}
//Should be tied for 1st Rank
var obj1 = {
name: "Person1",
rating: 99
}
//Should be 3rd Rank
var obj2 = {
name: "Person2",
rating: 50
}
//Should be 2nd Rank
var obj3 = {
name: "Person3",
rating: 98
}
//Should be 4th Rank
var obj4 = {
name: "Person4",
rating: 0
}
//Should be tied for 1st Rank
var obj5 = {
name: "Person5",
rating: 99
}
var clients = [obj1, obj2, obj3, obj4, obj5];
clients.sort((c, other) => other.rating - c.rating);
setRanks(clients);
console.log(clients);
console.log(getRank('Person5'));
2nd attempt: although not quite there - i argue separating the ranking in to a different property rather than rely on the indexOf to find ranking is the way to go. You then have something clearer to manipulate when there is a tie. Still working it. Will be watching for best solution
for(var i = 0; i < sorted.length; i++) {
// original ranking
sorted[i].rank = i + 1;
}
function sortRanking() {
for(i=0; i< sorted.length; i++) {
var current = sorted[i];
var next = sorted[i + 1];
if(next === undefined || next.rating !== current.rating) {
console.log("we are done");
return "done";
}
if(next.rating === current.rating) {
for(var j = next + 1; j < sorted.length; j++) {
sorted[j].rank = sorted[j-1].rank;
}
next.rank = current.rank;
}
}
}
sortRanking();
console.log(sorted);
1st attempt - After playing around with for a bit. Here is a solution adding from your original logic:
var clients = [o1, o2, o3, o4];
var sorted = [];
for (var i = 0; i < clients.length; i++)
sorted.push(clients[i]);
sorted.sort(function (a, b) {
return clients.rating - clients.rating;
});
function checkForTieAndRating(x) {
// x parameter for object of interest
// need to get the one in front to determine if it is tied
// get index of obj of interest
var indexOfInterest = clients.indexOf(x);
var indexOfBefore = indexOfCurrent -1;
// if obj of interest is ranked #1 then return
if(indexOfBefore < 0) {
return indexOfInterest + 1;
} else {
// get the actual object before this one so you can check rating. put in variable so you can compare.
var objBefore = clients[indexOfBefore];
var ratingOfObjBefore = objBefore.rating;
if(ratingOfObjBefore === x.rating)
return "Tied for" + indexOfInterest;
}
}
// check ranking and if tie
checkForTieAndRating(obj2);
// other issue going this route - would be to then 1) alter the objects ranking following the objs that are tied - to
//Possible alternative solution: After working and about to submit it - I think it would be better to add a ranking property after the sort and manipulate the rankings from there if there are any tied.
If you want several records on the same place, you should probably use an additional immediate array, effectively grouping the elements.
I will use lodash for convinience, you should get the idea.
_.chain(clients).groupBy('rating').pairs().sortBy(0).reverse().pluck(1).value();
You loose your ability to use indexOf at this point, so you need to write your own getRank.
Again, with the help of lodash
// returns zero when no rank is found
var getRank = function(sortedArray, object) {
return 1 + _.findIndex(sortedArray, function(list) {
return _.contains(list, object);
});
};
Full working fiddle: http://jsfiddle.net/4WJN3/1/
I needed a similar piece of code for an operations scheduling script I was writing. I used objects and their properties/keys, which can have any value and can be accessed whenever needed. Also, as far as I read in some articles, the search of properties in objects can be faster than search in arrays.
The script below has three simple steps:
sort the values (ascending or descending doesn't matter for the rest of the script)
find the ranks and number of occurrences for each value
replace the given values with ranks using the data from step 2
Note! The below script will not output duplicate ranks, but instead increments ranks for duplicate values/elements.
function rankArrayElements( toBeRanked ) {
// STEP 1
var toBeRankedSorted = toBeRanked.slice().sort( function( a,b ) { return b-a; } ); // sort descending
//var toBeRankedSorted = toBeRanked.slice().sort( function( a,b ) { return a-b; } ); // sort ascending
var ranks = {}; // each value from the input array will become a key here and have a rank assigned
var ranksCount = {}; // each value from the input array will become a key here and will count number of same elements
// STEP 2
for (var i = 0; i < toBeRankedSorted.length; i++) { // here we populate ranks and ranksCount
var currentValue = toBeRankedSorted[ i ].toString();
if ( toBeRankedSorted[ i ] != toBeRankedSorted[ i-1 ] ) ranks[ currentValue ] = i; // if the current value is the same as the previous one, then do not overwrite the rank that was originally assigned (in this way each unique value will have the lowest rank)
if ( ranksCount[ currentValue ] == undefined ) ranksCount[ currentValue ] = 1; // if this is the first time we iterate this value, then set count to 1
else ranksCount[ currentValue ]++; // else increment by one
}
var ranked = [];
// STEP 3
for (var i = toBeRanked.length - 1; i >= 0; i--) { // we need to iterate backwards because ranksCount starts with maximum values and decreases
var currentValue = toBeRanked[i].toString();
ranksCount[ currentValue ]--;
if ( ranksCount[ currentValue ] < 0 ) { // a check just in case but in theory it should never fail
console.error( "Negative rank count has been found which means something went wrong :(" );
return false;
}
ranked[ i ] = ranks[ currentValue ]; // start with the lowest rank for that value...
ranked[ i ] += ranksCount[ currentValue ]; // ...and then add the remaining number of duplicate values
}
return ranked;}
I also needed to do something else for my script.
The above output has the following meaning:
index - the ID of the element in the input array
value - the rank of the element from the input array
And I needed to basically 'swap the index with the value', so that I have a list of element IDs, arranged in the order of their ranks:
function convertRanksToListOfElementIDs( ranked ) { // elements with lower ranks will be first in the list
var list = [];
for (var rank = 0; rank < ranked.length; rank++) { // for each rank...
var rankFound = false;
for (var elementID = 0; elementID < ranked.length; elementID++) { // ...iterate the array...
if ( ranked[ elementID ] == rank ) { // ...and find the rank
if ( rankFound ) console.error( "Duplicate ranks found, rank = " + rank + ", elementID = " + elementID );
list[ rank ] = elementID;
rankFound = true;
}
}
if ( !rankFound ) console.error( "No rank found in ranked, rank = " + rank );
}
return list;}
And some examples:
ToBeRanked:
[36, 33, 6, 26, 6, 9, 27, 26, 19, 9]
[12, 12, 19, 22, 13, 13, 7, 6, 13, 5]
[30, 23, 10, 26, 18, 17, 20, 23, 18, 10]
[7, 7, 7, 7, 7, 7, 7, 7, 7, 7]
[7, 7, 7, 7, 7, 2, 2, 2, 2, 2]
[2, 2, 2, 2, 2, 7, 7, 7, 7, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
rankArrayElements( ToBeRanked ):
[0, 1, 8, 3, 9, 6, 2, 4, 5, 7]
[5, 6, 1, 0, 2, 3, 7, 8, 4, 9]
[0, 2, 8, 1, 5, 7, 4, 3, 6, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
convertRanksToListOfElementIDs( rankArrayElements( ToBeRanked ) ):
[0, 1, 6, 3, 7, 8, 5, 9, 2, 4]
[3, 2, 4, 5, 8, 0, 1, 6, 7, 9]
[0, 3, 1, 7, 6, 4, 8, 5, 2, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
[9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
function rank(arr) {
var ret = [];
var s = [];
var i = 0;
var _key_;
for (_key_ in arr) {
var v;
v = arr[_key_];
if (!s[v]) {
s[v] = ++i;
}
ret.push( {
'Mark': v,
'Rank': s[v]
});
}
return ret;
}
var marks = [
65,
41,
38,
38,
37,
37,
92,
84,
84,
84,
83
];
marks.sort(function(a, b) {
return b-a;
});
var rank = rank(marks);
console.log(rank);
Concise, efficient, flexible.
Items with same score have same ranks, yet the next different score get a rank shifted by n (based on index). Input must be a array sorted by values of the sourceColumn. Two versions of the code, pick the one you like :
for(){ } loop
array.map()
var studentsSortedByGrades = [
{ name: "A", grade: 5 },
{ name: "B", grade: 3 },
{ name: "C", grade: 3 },
{ name: "D", grade: 2 },
];
var addRankFORLOOP = function(sortedArr,sourceColumn,newColumn){
for(var i = 0; i<sortedArr.length; i++){ //
sortedArr[i][newColumn] =
i===0 || sortedArr[i][sourceColumn] !== sortedArr[i-1][sourceColumn] ? i+1 // anytime new grade appears, rank=i
: sortedArr[i-1][newColumn] // elseIf: equal grade, then equal rank
}
return sortedArr;
};
/*//OR
var addRankMAP = function(sortedArr,sourceColumn,newColumn){
return sortedArr.map((item,i) => {
item[newColumn] = i===0 || sortedArr[i][sourceColumn] !== sortedArr[i-1][sourceColumn] ? i+1 // anytime new grade appears, rank=i
: sortedArr[i-1][newColumn] // elseIf: equal grade, then equal rank
return item; })
}; /**/
var withRanks = addRankFORLOOP(studentsSortedByGrades,'grade','rank');
console.log(withRanks) // ranks: 1,2,2,4
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}