Split randomly Array of Object in two equal arrays - javascript

I have this array of object;
let persons = [
{id: 1, name: "..."},
{id: 2, name: "..."},
{id: 3, name: "..."},
{id: 4, name: "..."},
{id: 5, name: "..."},
{id: 6, name: "..."},
{id: 7, name: "..."},
{id: 8, name: "..."}
]
I would like to split this array in two array of equal length. each time a execute the function which split the array It should return a random data in each array not the same list of object.
I try with this function
function splitArr(data, part) {
let list1 = [];
let list2 = [];
for(let i = 0; i < data.length ; i++) {
let random = Math.floor(Math.random() * data.length);
if(random % 2 === 0) {
list1.push(data[i]);
} else {
list2.push(data[i]);
}
}
return [list1, list2];
}
It isn't obvious that the function will return exactly array of equal length each time. Some time it return array of 2 and 6 element not equal.

Just shuffle the array randomly and then splice the array in half.
For shuffling an array take the solution provided here.
function shuffle(a) {
var j, x, i;
for (i = a.length - 1; i > 0; i--) {
j = Math.floor(Math.random() * (i + 1));
x = a[i];
a[i] = a[j];
a[j] = x;
}
return a;
}
For getting the two lists from that, do:
let list2 = shuffle([...data]); // spread to avoid mutating the original
let list1 = list2.splice(0, data.length >> 1);
The shift operator >> is used to get the truncated half of the array length.

I think that fastest and more reliable would be to use native array methods in this case.
I would recommend to go with slice method like below:
function splitArr(data) {
const arrLength = data.length;
const firstArr = data.slice(0, arrLength/2);
const secArr = data.slice(arrLength / 2, arrLength);
return [firstArr, secArr];
}
This way you got an universal function that will always return two arrays of same length.
You can experiment with Math.min() and Math.ceil in edge cases (like with arrays of uneven lenght).

You can do this with randojs.com really easily using the randoSequence function, which does not affect the original array. Then, use the slice function to split the arrays, and the bitwise operator >> to handle original arrays of odd length.
function shuffleAndSplit(arr){
var shuffled = randoSequence(arr);
shuffled.forEach((item, i) => {shuffled[i] = item.value;});
return [shuffled.slice(0, shuffled.length >> 1), shuffled.slice(shuffled.length >> 1)];
}
console.log(shuffleAndSplit(["a", "b", "c", "d", "e", "f", "g"]));
<script src="https://randojs.com/1.0.0.js"></script>
This could be even simpler if you used the array map function, but that has some issues in Internet Explorer. If you don't care about IE, here's how you'd do this with map:
function shuffleAndSplit(arr){
var shuffled = randoSequence(arr).map(item => item.value);
return [shuffled.slice(0, shuffled.length >> 1), shuffled.slice(shuffled.length >> 1)];
}
console.log(shuffleAndSplit(["a", "b", "c", "d", "e", "f", "g"]));
<script src="https://randojs.com/1.0.0.js"></script>

If you only wish to split it into two seperate part, you could use splice.
It takes two parameters, three if you wish to replace elements, the first one is the starting splice index. The second is the number of element to remove. The function will returns the removed element, spliting your array in half. And since the splice function is removing element from the original array, you will be left with two arrays of equals length ( if you have an even number of element ).
As for the randomess of your array, you could simply shuffle it before splitting it. Here i've used Jeff's answer
/**
* https://stackoverflow.com/a/6274381/5784924
* Shuffles array in place. ES6 version
* #param {Array} a items An array containing the items.
*/
function shuffle(a) {
for (let i = a.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[a[i], a[j]] = [a[j], a[i]];
}
return a;
}
let persons = shuffle([
{id: 1, name: "..."},
{id: 2, name: "..."},
{id: 3, name: "..."},
{id: 4, name: "..."},
{id: 5, name: "..."},
{id: 6, name: "..."},
{id: 7, name: "..."},
{id: 8, name: "..."}
]);
let firstArray = persons.splice(0, persons.length / 2);
console.log(firstArray.map((item) => item.id), persons.map((item) => item.id));

the problem with your approach is this:
if(random % 2 === 0) {
list1.push(data[i]);
} else {
list2.push(data[i]);
}
you are trying to insert in a random array, but you are not handling if that array has the same length than the other ( and it will be hard to do, you will lose your random intentions)
it is better to insert random items in each one of the arrays, for each iteration.
let persons = [
{id: 1, name: "..."},
{id: 2, name: "..."},
{id: 3, name: "..."},
{id: 4, name: "..."},
{id: 5, name: "..."},
{id: 6, name: "..."},
{id: 7, name: "..."},
{id: 8, name: "..."}
]
function splitArr(data, part) {
let list1 = [];
let list2 = [];
let isPair = false;
while(data.length > 0){
const randomEntry = Math.floor(Math.random() * data.length);
const arrayToPush = isPair?list1:list2;
arrayToPush.push(data[randomEntry]);
data.splice(randomEntry, 1);
isPair = !isPair;
}
console.log(list1.length, list2.length)
return [list1, list2];
}
splitArr(persons)

Does this helps you ?
function splitArr(data, count_decks) {
data = data.slice()
const decks = []
let i = 0
while (data.length) {
if (!Array.isArray(decks[i])) decks[i] = []
decks[i].push(data.splice(Math.random()*data.length, 1)[0])
i = (i+1) % count_decks
}
return decks
}
splitArr(persons, 2) // here 2 for 2 decks

Related

How to create array of objects through map?

I would like to have multiple arrays of objects like this.
E.g:
const pets = [
{
name: "cat",
age: 4
},
{
name: "dog",
age: 6
}
]
But I want to create it using a map. So I was trying something like this.
let pets = [];
pets.map((item) => {
return (
item.push({
name: "cat",
age: 4
}, {
name: "dog",
age: 6
})
)
})
By this method, I'm getting an empty array.
So assuming this is incorrect, how would I go on and make this through a map.
Please any help would be appreciated.
first of all map works by looping through an array but you have empty array let pets = []; so the loop doesn't even start ! that's why you are getting empty array
Secondly map essentially is a method through which we can create a new array with the help of an existing array so you have chosen a wrong way!
example of map
const fruits = ["Mango", "Apple", "Banana", "Pineapple", "Orange"];
console.log(fruits);
const uppercaseFruits = fruits.map((fruit)=>{
return fruit.toUpperCase(); // this thing will be added to new array in every iteration
});
console.log(uppercaseFruits);
but still ....
let pets = [""]; // an item so that loop can start
const myPets = pets.map((item) => {
return (
([{
name: "cat",
age: 4
},{
name: "dog",
age: 6
}])
)
})
console.log(myPets)
//Usage of map: for example
let array = [1, 2, 3, 4, 5];
let newArray = array.map((item) => {
return item * item;
})
console.log(newArray) // [1, 4, 9, 16, 25]
map will not change the original array, if you don't assign a value to it, the original array will never be affected
And if you want to get what you want you use RANDOM like this
//random String
function randomString(e) {
e = e || 32;
var t = "ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678",
a = t.length,
n = "";
for (i = 0; i < e; i++) n += t.charAt(Math.floor(Math.random() * a));
return n
}
//random Number
function GetRandomNum(Min,Max)
{
var Range = Max - Min;
var Rand = Math.random();
return(Min + Math.round(Rand * Range));
}
var num = GetRandomNum(10000,999999);
alert(num);
Then you can combine random strings and random numbers into a new Object through a function

Javascript: Split an array according to a pattern: items 1, 5, 10, then 2, 6, 11, then 3, 7, 12

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

Reduce an Array of Arrays by value

I have an array of arrays similar to the structure below. I am trying to reduce the array as efficiently as possible based on the Company Name (ex. Company A). So basically, where the company names are the same, combine the inner array so that the numbers in each position get added to the matching array's numbers in the corresponding position. Also if one of the arrays has a missing email or phone, take the email or phone position that has a value. The resultArray at the bottom shows the result I am trying to achieve.
*Note - I don't know the length of numbers following a company. The length is dynamically set, but the length of each inner array will always be the same. So sometimes all the innerArray's are 6 values, other times they could be 20 values in length.
var array = [
[Company A, A-Email, A-Phone, 2, 5, 10],
[Company A, A-Email, , 1, 10, 7],
[Company A, , A-Phone, 3, 2, 4],
[Company B, B-Email, , 1, 10, 7],
[Company B, B-Email, B-Phone, 5, 10, 8],
[Company C, C-Email, C-Phone, 3, 2, 1]
]
var resultArray = [
[Company A, A-Email, A-Phone, 6, 17, 21],
[Company B, B-Email, B-Phone, 6, 20, 15],
[Company C, C-Email, C-Phone, 3, 2, 1]
]
So originally I was trying something like this because the array had already been sorted by company name:
for (var i = 0; i < array.length - 1; i++) {
var firstArray = array[i]
var nextArray = array[i + 1]
if (nextArray[0] == firstArray[0]) {
for (var t = 3; t <= firstArray.length; t++) {
firstArray[t] = firstArray[t] + nextArray[t]
}
resultArray.push(firstArray);
} else {continue;}
I have a large set of data and doing it this way was really operation heavy and my function timed out so I'm not completely sure if it even worked. I started to try to do a reduce method with a hash table but I couldn't quite figure it out. Any idea's on how to do this most efficiently?
Also I can't use jQuery, so purely vanilla javascript please.
You can use ES6 reduce to summarize the array into an object. And use Object.values to convert the object into an array.
Note: Fiddle does not (currently) working. So you might need to test it on your browser.
var array=[['Company A','A-Email','A-Phone',2,5,10],['Company A','A-Email',,1,10,7],['Company A',,'A-Phone',3,2,4],['Company B','B-Email',,1,10,7],['Company B','B-Email','B-Phone',5,10,8],['Company C','C-Email','C-Phone',3,2,1]];
var resultArray = Object.values(array.reduce((c, v) => {
c[v[0]] = c[v[0]] || [v[0], null, null].concat(new Array(v.length - 3).fill(0));
c[v[0]][1] = c[v[0]][1] || v[1]; //Update Email
c[v[0]][2] = c[v[0]][2] || v[2]; //Update Phone
//Loop thru the numbers and add
for (var i = 3; i < v.length; i++) c[v[0]][i] += ( v[i] || 0 );
return c;
}, {}));
console.log(resultArray);
Hash tables would be the correct approach (assuming 'Company A' etc are strings; otherwise it's a Map).
var companies = {};
for ( var i = 0; i < array.length; i++ ) {
var item = array[ i ];
var name = item[ 0 ];
var phone = item[ 1 ];
var num1 = item[ 2 ];
var num2 = item[ 3 ];
var num3 = item[ 4 ];
if ( companies[ name ] ) {
var record = companies[ name ];
record[ 1 ] = record[ 1 ] || email;
record[ 2 ] = record[ 2 ] || phone;
record[ 2 ] += num1;
record[ 3 ] += num2;
record[ 4 ] += num3;
} else {
companies[ name ] = [ name, email, phone, num1, num2, num3 ]
}
}
var resultArray = Object.values( companies );
You can try something like below. The main point is to use reduce() to get an object full of correct data then flatten it back into arrays using Object.values().
//initial data
var data = [
['Company A', 'A - Email', 'A - Phone', 2, 5, 10],
['Company A', 'A - Email', , 1, 10, 7],
['Company A', , 'A - Phone', 3, 2, 4],
['Company B', 'B - Email', , 1, 10, 7],
['Company B', 'B - Email', 'B - Phone', 5, 10, 8],
['Company C', 'C - Email', 'C - Phone', 3, 2, 1]
];
//reduce function to organize data
var reducer = function(accumulator, currentValue, currentIdx) {
var [companyId, email, phone, i, j, k] = currentValue;
accumulator[companyId] = {
email: !accumulator[companyId] ? email : !accumulator[companyId].email ? email : accumulator[companyId].email,
phone: !accumulator[companyId] ? phone : !accumulator[companyId].phone ? phone : accumulator[companyId].phone,
i: (accumulator[companyId] ? accumulator[companyId].i : 0) + i,
j: (accumulator[companyId] ? accumulator[companyId].j : 0) + j,
k: (accumulator[companyId] ? accumulator[companyId].k : 0) + k
}
return accumulator;
}
var rawData = data.reduce(reducer, {});
//organize data back into array of arrays
var formattedData = [];
for (key in rawData) {
formattedData.push([key].concat(Object.values(rawData[key])));
}
console.log(formattedData);

Sort array using the positions

I've got an array as below.
var FruitArr = [5, "Mango", 3, "Apple", 2, "Lychee", 1, "Banana", 4, "Pineapple"];
How can I sort the fruit names according to the number before it and add to an empty array? The array has been stored as position , item.
The expected output is
var newFruitArr = ["Banana", "Lychee", "Apple", "Pineapple", "Mango"];
EDIT:
The reason for having items as it is shown: In my actual code the fruit names are base64 url string which is created on the fly. The base64 creating depends based on the image. Therefore I couldn't think of a better way of adding the url strings in to the array. So I added items to the array as 'desired position', 'base64 string'. I thought of sorting them once all conversions are done. I did use .splice() which did not work as expected because of the above reason.
There is no need to sort, you already have the indexes in your input array.
Just preallocate your new array and fill it.
var fruits = [2, "apple", 1, "orange"],
fruitsLength = fruits.length;
var newFruitArr = new Array(fruitsLength / 2);
for (var i = 0; i < fruitsLength; i += 2)
newFruitArr[fruits[i] - 1] = fruits[i + 1];
Does this fit your need ?
function sort (arr) {
var min, minId = -1, output = [];
while (arr.length >= 2) {
for (var i = 0; i < arr.length; i += 2) {
if (arr[i] < min || minId == -1) {
minId = i;
min = arr[i];
}
}
output.push(arr[minId + 1]);
arr.splice(minId, 2);
minId = -1;
}
return output;
}
It search for the minimum number, push the corresponding fruit to the output and remove the couple from the input array, until there's nothing in it. Quite simple, surely not the most effective solution.
You have to convert your array to a form easy to use with sort method.
Here is the code to do so:
var result = [];
FruitArr.forEach(function (el, i) {
if (i % 2) result.push({value: el, weight: FruitArr[i-1]});
});
The result array will be:
[{value: "Mango", weight: 5}, {value: "Apple", weight: 3}, {value: "Lychee", weight: 2}, {value: "Bananna", weight: 1}, {value: "Pineapple", weight: 4}];
which easy to sort with sort method.
I actually prefer insertion-sort-algo to sort an array because of performance issues:
var arr = [5, "Mango", 3, "Apple", 2, "Lychee", 1, "Bananna", 4, "Pineapple"];
var groups = [];
for(var f=0; f < arr.length; f+=2)groups.push([arr[f],arr[f+1]]);
function insertion_sort(array){
for(var o=1; o < array.length;o++){
for(var i=o; i>0 && array[i][0] < array[i-1][0];i--){
var tmp = array[i];
array[i] = array[i-1];
array[i-1] = tmp;
}
}
return array;
}
insertion_sort(groups); // [[1, "Bananna"], [2, "Lychee"], [3, "Apple"], [4, "Pineapple"], [5, "Mango"]]

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