sort by number of occurrence(count) in Javascript array - javascript

I am new to Jquery and Javascript. Can someone please help me with Jquery sorting based on number of occurrence(count) in array. I tried various sorting methods but none of them worked.
I have an array in Javascript which is
allTypesArray = ["4", "4","2", "2", "2", "6", "2", "6", "6"]
// here 2 is printed four times, 6 is printed thrice, and 4 is printed twice
I need output like this
newTypesArray = ["2","6","4"]
I tried
function array_count_values(e) {
var t = {}, n = "",
r = "";
var i = function (e) {
var t = typeof e;
t = t.toLowerCase();
if (t === "object") {
t = "array"
}
return t
};
var s = function (e) {
switch (typeof e) {
case "number":
if (Math.floor(e) !== e) {
return
};
case "string":
if (e in this && this.hasOwnProperty(e)) {
++this[e]
} else {
this[e] = 1
}
}
};
r = i(e);
if (r === "array") {
for (n in e) {
if (e.hasOwnProperty(n)) {
s.call(t, e[n])
}
}
}
return t
}
6: 3
}
output is
{4: 2, 2: 6, 6:3}

I don't think there's a direct solution in one step and of course it's not just a sort (a sort doesn't remove elements). A way to do this would be to build an intermediary map of objects to store the counts :
var allTypesArray = ["4", "4","2", "2", "2", "6", "2", "6", "6"];
var s = allTypesArray.reduce(function(m,v){
m[v] = (m[v]||0)+1; return m;
}, {}); // builds {2: 4, 4: 2, 6: 3}
var a = [];
for (k in s) a.push({k:k,n:s[k]});
// now we have [{"k":"2","n":4},{"k":"4","n":2},{"k":"6","n":3}]
a.sort(function(a,b){ return b.n-a.n });
a = a.map(function(a) { return a.k });
Note that you don't need jQuery here. When you don't manipulate the DOM, you rarely need it.

Just adding my idea as well (a bit too late)
var allTypesArray = ["4", "4", "2", "2", "2", "6", "2", "6", "6"];
var map = allTypesArray.reduce(function(p, c) {
p[c] = (p[c] || 0) + 1;
return p;
}, {});
var newTypesArray = Object.keys(map).sort(function(a, b) {
return map[b] - map[a];
});
console.log(newTypesArray)

I don't think jquery is needed here.
There are several great answers to this question already, but I have found reliability to be an issue in some browsers (namely Safari 10 -- though there could be others).
A somewhat ugly, but seemingly reliable, way to solve this is as follows:
function uniqueCountPreserve(inputArray){
//Sorts the input array by the number of time
//each element appears (largest to smallest)
//Count the number of times each item
//in the array occurs and save the counts to an object
var arrayItemCounts = {};
for (var i in inputArray){
if (!(arrayItemCounts.hasOwnProperty(inputArray[i]))){
arrayItemCounts[inputArray[i]] = 1
} else {
arrayItemCounts[inputArray[i]] += 1
}
}
//Sort the keys by value (smallest to largest)
//please see Markus R's answer at: http://stackoverflow.com/a/16794116/4898004
var keysByCount = Object.keys(arrayItemCounts).sort(function(a, b){
return arrayItemCounts[a]-arrayItemCounts[b];
});
//Reverse the Array and Return
return(keysByCount.reverse())
}
Test
uniqueCountPreserve(allTypesArray)
//["2", "6", "4"]

This is the function i use to do this kind of stuff:
function orderArr(obj){
const tagsArr = Object.keys(obj)
const countArr = Object.values(obj).sort((a,b)=> b-a)
const orderedArr = []
countArr.forEach((count)=>{
tagsArr.forEach((tag)=>{
if(obj[tag] == count && !orderedArr.includes(tag)){
orderedArr.push(tag)
}
})
})
return orderedArr
}

const allTypesArray = ["4", "4","2", "2", "2", "6", "2", "6", "6"]
const singles = [...new Set(allTypesArray)]
const sortedSingles = singles.sort((a,b) => a - b)
console.log(sortedSingles)
Set objects are collections of values. A value in the Set may only occur once; it is unique in the Set's collection.
The singles variable spreads all of the unique values from allTypesArray using the Set object with the spread operator inside of an array.
The sortedSingles variable sorts the values of the singles array in ascending order by comparing the numbers.

Not sure if there's enough neat answers here, this is what I came up with:
Fill an object with counts for each of the elements:
let array = ['4', '4', '2', '2', '2', '6', '2', '6', '6'];
let arrayCounts = {}
for (j in array) arrayCounts[array[j]] ? arrayCounts[array[j]].count++ : arrayCounts[array[j]] = { val: array[j], count: 1 };
/* arrayCounts = {
'2': { val: '2', count: 4 },
'6': { val: '4', count: 2 },
'4': { val: '6', count: 3 }
} */
For the values in that new object, sort them by .count, and map() them into a new array (with just the values):
let sortedArray = Object.values(arrayCounts).sort(function(a,b) { return b.count - a.count }).map(({ val }) => val);
/* sortedArray = [ '2', '6', '4' ] */
Altogether:
let arrayCounts = {}
for (j in array) arrayCounts[array[j]] ? arrayCounts[array[j]].count++ : arrayCounts[array[j]] = { val: array[j], count: 1 };
let sortedArray = Object.values(arrayCounts)
.sort(function(a,b) { return b.count - a.count })
.map(({ val }); => val);

var number = [22,44,55,11,33,99,77,88];
for (var i = 0;i<number.length;i++) {
for (var j=0;j<number.length;j++){
if (number[j]>number[j+1]) {
var primary = number[j];
number[j] = number[j+1];
number[j+1] = primary;
}
}
}
document.write(number);

Related

How to find first n array items that match a condition without looping through entire array

I am trying to find the first 100 items in a very large array that match a condition and I'd prefer to end the loop once I've found those 100 for efficiency sake, since the method for matching the items is expensive.
The problem is that doing:
const results = largeArray.filter(item => checkItemValidity(item)).slice(0, 100);
will find all the results in the large array before returning the first 100, which is too wasteful for me.
And doing this:
const results = largeArray.slice(0, 100).filter(item => checkItemValidity(item));
could return less than 100 results.
Please what's the most efficient way of doing this?
Rather than putting a conditional and break inside a for loop, just add the extra length check in the for condition itself
const data = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14"],
isValid = n => !(n%2),
res = [],
max = 5;
for (let i = 0; i < data.length && res.length < max; i++) {
isValid(data[i]) && res.push(data[i]);
}
console.log(res)
There are several array methods that will exit early Array.some, Array.every, Array.find, Array.findIndex
You can use them to stop the iteration when you need.
Example using Array.find
const data = [-1,-6,-6,-6,1,-2,2,3,4,-5,5,6,7,-8,8,9,-10,10,11,-1,2,3,4,5,-6,7,8,-9,10,11,];
const first10 = [];
data.find(item => (item > 0 && first10.push(item), first10.length >= 10));
console.log(first10 +"");
You ocul take a generator function and exit of the wanted length is found.
function* getFirst(array, fn, n) {
let i = 0;
while (i < array.length) {
if (fn(array[i])) {
yield array[i];
if (!--n) return;
}
i++;
}
}
const
expFn = x => x % 2 === 0,
array = [2, 4, 5, 1, 3, 7, 9, 10, 6, 0];
console.log(...getFirst(array, expFn, 4));
The most efficient way would be to use a for construct instead of a function and then break out when you have reached your limit.
const results = []
for (const item of largeArray) {
// End the loop
if(results.length === 100) break
// Add items to the results
checkItemValidity(item) && results.push(item)
}
console.log(results)
You can use something like this. I.e. Finding the first 5 odd numbers
var data = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14"]
var filterData = [];
for (let i = 0; i < data.length; i++) {
if (data[i] % 2 === 0) {
filterData.push(data[i]);
}
// Track count console.log(i)
if (filterData.length === 5)
break;
}
console.log(filterData)
You would need to do a standard "for" loop as filter function returns a new array of the given array so here is how I would approach this:
let largeArray = ["foo", "bar", "foo", "bar"]
let validateArray = ["foo"]
let newArray = []
for (let item of largeArray){
//change number to how many items needed
if (newArray.length === 2){
console.log(newArray)
// Output would be ["foo", "foo"]
break;
}
// If you have a custom function to return true or false replace here
if (validateArray.includes(item)){
newArray.push(item);
}
}
If you are not returning strings you might need to create a custom function to return true or false depending on how you would define a validate data
I'll recommend you use findIndex, the problem with the some and every is that if the array is empty it will return true
Reference: Why does Array.prototype.every return true on an empty array?
I'm going to assume that what is expensive is the function that you are using to filter items in the array, not the actual iteration.
In that case, I would recommend using the array objects .reduce() method. Inside the function you pass to it, you can check whether your accumulator is already large enough. If it isn't, then you can conditionally add to it.
const results = largeArray.reduce(( accumulator , item )=> {
if (accumulator.length <= 100) {
if (checkItemValidity(item)) {
accumulator.push(item)
}
}
return accumulator
}, [])

Finding the index of an object within an array efficiently

I am trying to find the index of an object within an array. I know there is a way to do this with underscore.js but I am trying to find an efficient way without underscore.js. Here is what I have :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var rx = /\{.*?\}/; // regex: finds string that starts with { and ends with }
var arr = []; // creates new array
var str = JSON.stringify(arrayOfObjs); // turns array of objects into a string
for (i = 0; i < arrayOfObjs.length; i++) { // loops through array of objects
arr.push(str.match(rx)[0]); // pushes matched string into new array
str = str.replace(rx, ''); // removes matched string from str
}
var Index = arr.indexOf(JSON.stringify(key)); // stringfy key and finds index of key in the new array
alert(Index);
}
FindIndex({"ob2": "test1"});
JSFIDDLE
This works but I am afraid it isn't very efficient. Any alternatives?
Here's one way to do it, somewhat reliably and a little more efficiently, using some() and stopping as soon as the objects don't match etc.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
var index = -1;
arrayOfObjs.some(function(item, i) {
var result = Object.keys(key).some(function(oKey) {
return (oKey in item && item[oKey] === key[oKey]);
});
if (result) index = i;
return result;
});
return index;
}
var index = FindIndex({"ob2": "test1"});
document.body.innerHTML = "'{\"ob2\": \"test1\"}' is at index : " + index;
A hash table with an example of access.
var arrayOfObjs = [{ "obj1": "test1" }, { "obj2": "test1" }, { "obj1": "test3" }],
hash = {};
arrayOfObjs.forEach(function (a, i) {
Object.keys(a).forEach(function (k) {
hash[k] = hash[k] || {};
hash[k][a[k]] = i;
});
});
document.write('<pre>' + JSON.stringify(hash['obj2']['test1'], 0, 4) + '</pre>');
document.write('<pre>' + JSON.stringify(hash, 0, 4) + '</pre>');
One way of doing this would be to use every to see if each key in the "filter" has a matching, correct value in an object. every ensures that the loop stops as soon as it finds a mismatched or missing value.
function log(msg) {
document.querySelector('pre').innerHTML += msg + '\n';
}
var arr = [
{
a: 1
},
{
b: 2
},
{
c: 3,
d: 4
},
{
a: 1 // Will never reach this since it finds the first occurrence
}
];
function getIndex(filter) {
var keys = Object.keys(filter);
for (var i = 0, len = arr.length; i < len; i++) {
var obj = arr[i];
var match = keys.every(function(key) {
return filter[key] === obj[key];
});
if (match) {
return i;
}
}
return -1;
}
log(getIndex({ a: 1 }));
log(getIndex({ b: 2 }));
log(getIndex({ c: 3 }));
log(getIndex({ c: 3, d: 4 }));
log(getIndex({ e: 5 })); // Doesn't exist, won't find it
<pre></pre>
For an alternative to your customly built approach, lodash's findIndex method does exactly this for you:
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
_.findIndex(arrayOfObjs, {"ob2": "test1"}); // => 1
Since testing equality on two different objects will always return false you could first test keys and then values ,
using reduce :
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1" , k2:2
}, {
"ob1": "test3"
}];
function getI( obj, arr){
const checkK= Object.keys(obj);
return arr.reduce((ac,x,i) => {
if ( checkK.every(z => x[z] && obj[z] === x[z]) )
ac.push(i);
return ac;
},[])
}
document.write( 'result is :'+ getI({ob2:'test1', k2:2},arrayOfObjs))
findIndex won't work in old browsers, but was designed for this specific purpose.
var arrayOfObjs = [{
"ob1": "test1"
}, {
"ob2": "test1"
}, {
"ob1": "test3"
}];
function FindIndex(key) {
return arrayOfObjs.findIndex(
obj => Object.keys(key).every(name => key[name] === obj[name])
);
}
alert(FindIndex({"ob2": "test1"})); // 1

Splicing objects from an array Javascript

I am trying to remove an object from an array if a particular value for a key matches a given string:
Example data:
array = [{_id: "abc", test: "123"},
{_id: "def", test: "123"},
{_id: "ghi", test: "123"}];
Here is my loop:
for (var i = 0; i < array.length; i++) {
var x = "123"
if (array[i].test == x) {
array.splice(i, 1)
}
}
This should return an empty array but it's leaving one object in the array (the last one) and I've got no clue why.
You must not increment i if you remove an element.
Suppose the array has two matching elements.
The first iteration, i = 0
i ==>> element 1
element 2
You then remove element 1, and increment i
element 2
i ==>>
There are many ways you can correct this. Here's an example that replaces your for loop with a while loop.
var i = 0;
while (i < array.length) {
var x = "123"
if (array[i].test == x) {
array.splice(i, 1)
} else {
++i;
}
}
Figured it out:
array = array.filter(function(array) {
var x = "123";
return array.test !== x;
})
array = [
{id: "abc", test: "123"},
{id: "def", test: "456"},
{id: "ghi", test: "789"}
];
let x = "123";
let i = array.findIndex(data => data.test === x);
if (i !== -1) {
array.splice(i, 1);
} else ...
console.log(array);
I have written a function that takes the index and event values from the html and processes in ts
app.component.html
<tr *ngFor="let container of containers; let i = index;"
[(ngModel)]="miningname"
ngDefaultControl>
<td>
<input *ngIf="addMore" type="text" class = "form-control"
placeholder="Key"
[(ngModel)]="container.key2Name"
(ngModelChange)="textKeyChangedMore($event,i)">
</td></tr>
app.component.ts
textKeyChangedMore(eventMore, indexMore) {
var valueLength = this.selectedAPIName.length
var i = indexMore+1;
if (this.selectedAPIName[i].value == undefined ) {
this.selectedAPIName.splice(indexMore + 1, 1, { key: eventMore, value: undefined });
}
else if (this.selectedAPIName[i].value !== undefined) {
this.selectedAPIName.splice(indexMore + 1, 1, { key: eventMore, value: this.selectedAPIName[i].value });
}
}

merging two array of objects with custom compare function with javascript

I have two array of objects like:
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
and:
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
I need to merge this two array in one array and sum the "count" values in returned array when the "title" properties is same:
the last answer must be:
[{title:"name1",count:5},{title:"name2",count:8},{title:"name3",count:5},{title:"name4",count:3},{title:"name5",count:8}]
how can i do this???
You can use Array#forEach and Array#some to achieve a result
var M = A.concat(B)
var C = [];
M.forEach(function(a) {
var index;
if (C.some(function(c, i) { index = i; return a.title == c.title; })) {
C[index].count += a.count;
} else {
C.push(a);
}
});
console.log(C); // as you expect
Solution with Array.concat and Array.map functions:
var merged = A.concat(B), titles = [], result = [];
merged.map(function(obj){
if (titles.indexOf(obj.title) === -1) {
titles.push(obj.title);
result.push(obj);
} else {
result[titles.indexOf(obj.title)]['count'] += obj['count'];
}
});
console.log(result); // will output the expected array of objects
It can be done like this https://jsfiddle.net/menm9xeo/
var noMatch;
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
//for each A, loop through B's. If a match is found combine the Counts in A.
for(var i=0;i<A.length;i++){
for(var j=0;j<B.length;j++){
if(A[i].title == B[j].title){
A[i].count += B[j].count;
}
}
}
//find all B's that were not combined with A in the previous step, and push them into A.
for(var i=0;i<B.length;i++){
noMatch = true;
for(var j=0;j<A.length;j++){
if(B[i].title == A[j].title){
B[i].count += A[j].count;
noMatch = false;
}
}
if(noMatch){A.push(B[i]);}
}
Heres a simple 3 line answer (minus the A/B vars); utilizes the fact that objects must have unique keys
var A = [{title:"name1",count:5},{title:"name2",count:1},{title:"name3",count:3}];
var B = [{title:"name2",count:7},{title:"name3",count:2},{title:"name4",count:3},{title:"name5",count:8}];
var o = {};
A.concat(B).forEach(function(a){o[a.title] = o.hasOwnProperty(a.title)? o[a.title]+a.count: a.count});
var AB = Object.keys(o).map(function(j){ return {title:j,count:o[j]} });
This proposal is merging and counting with a temporary object and Array#forEach()
The forEach() method executes a provided function once per array element.
var arrayA = [{ title: "name1", count: 5 }, { title: "name2", count: 1 }, { title: "name3", count: 3 }],
arrayB = [{ title: "name2", count: 7 }, { title: "name3", count: 2 }, { title: "name4", count: 3 }, { title: "name5", count: 8 }],
result = function (array) {
var o = {}, r = [];
array.forEach(function (a) {
if (!(a.title in o)) {
o[a.title] = { title: a.title, count: 0 };
r.push(o[a.title]);
}
o[a.title].count += a.count;
});
return r;
}(arrayA.concat(arrayB));
document.write('<pre>' + JSON.stringify(result, 0, 4) + '</pre>');
Using lodash ._concat function :
var result = _.concat(A, B);
Fiddle

Javascript algorithm to find elements in array that are not in another array

I'm looking for a good algorithm to get all the elements in one array that are not elements in another array. So given these arrays:
var x = ["a","b","c","t"];
var ​​​​​​​​​y = [​​​​​​​"d","a","t","e","g"];
I want to end up with this array:
var z = ["d","e","g"];
I'm using jquery, so I can take advantage of $.each() and $.inArray(). Here's the solution I've come up with, but it seems like there should be a better way.
// goal is to get rid of values in y if they exist in x
var x = ["a","b","c","t"];
var y = ["d","a","t","e","g"];
var z = [];
$.each(y, function(idx, value){
if ($.inArray(value,x) == -1) {
z.push(value);
}
});
​alert(z); // should be ["d","e","g"]
Here is the code in action. Any ideas?
in ES6 simply
const a1 = ["a", "b", "c", "t"];
const a2 = ["d", "a", "t", "e", "g"];
console.log( a2.filter(x => !a1.includes(x)) );
(another option is a2.filter(x => a1.indexOf(x)===-1) )
Late answer with the new ECMA5 javascript:
var x = ["a","b","c","t"];
var y = ["d","a","t","e","g"];
myArray = y.filter( function( el ) {
return x.indexOf( el ) < 0;
});
var z = $.grep(y, function(el){return $.inArray(el, x) == -1});
Also, that method name is too short for its own good. I would expect it to mean isElementInArray, not indexOf.
For a demo with objects, see http://jsfiddle.net/xBDz3/6/
Here's an alternative using underscore.js:
function inAButNotInB(A, B) {
return _.filter(A, function (a) {
return !_.contains(B, a);
});
}
I am quite late now but maybe it will be helpful for someone.
If the array is not just a simple array but an array of objects then the following can be used:
var arr1 = [
{
"prop1": "value1",
"prop2": "value2",
},
{
"prop1": "value3",
"prop2": "value4",
},
{
"prop1": "value5",
"prop2": "value6",
},
];
var arr2 = ['value1','value3', 'newValue'];
// finds all the elements of arr2 that are not in arr1
arr2.filter(
val => !arr1.find( arr1Obj => arr1Obj.prop1 === val)
); // outputs "newValue"
This is a late answer, but it uses no libraries so some may find it helpful.
/**
* Returns a non-destructive Array of elements that are not found in
* any of the parameter arrays.
*
* #param {...Array} var_args Arrays to compare.
*/
Array.prototype.uniqueFrom = function() {
if (!arguments.length)
return [];
var a1 = this.slice(0); // Start with a copy
for (var n=0; n < arguments.length; n++) {
var a2 = arguments[n];
if (!(a2 instanceof Array))
throw new TypeError( 'argument ['+n+'] must be Array' );
for(var i=0; i<a2.length; i++) {
var index = a1.indexOf(a2[i]);
if (index > -1) {
a1.splice(index, 1);
}
}
}
return a1;
}
Example:
var sheetUsers = ['joe#example.com','fred#example.com','sam#example.com'];
var siteViewers = ['joe#example.com','fred#example.com','lucy#example.com'];
var viewersToAdd = sheetUsers.uniqueFrom(siteViewers); // [sam#example.com]
var viewersToRemove = siteViewers.uniqueFrom(sheetUsers); // [lucy#example.com]
findDiff = (A, B) => {
return A.filter(function (a) {
return !B.includes(a);
});
}
Make sorted copies of the arrays first. If the top elements are equal, remove them both. Otherwise remove the element that is less and add it to your result array. If one array is empty, then add the rest of the other array to the result and finish. You can iterate through the sorted arrays instead of removing elements.
// assume x and y are sorted
xi = 0; yi = 0; xc = x.length; yc = y.length;
while ( xi < xc && yi < yc ) {
if ( x[xi] == y[yi] ) {
xi += 1;
yi += 1;
} else if ( x[xi] < y[yi] ) {
z.push( x[xi] );
xi += 1;
} else {
z.push( y[yi] );
yi += 1;
}
}
// add remainder of x and y to z. one or both will be empty.
Maybe jLinq can help you?
It lets you run queries like this against javascript objects.
For example:
var users = [ { name: "jacob", age: 25 }, { name: "bob" , age: 30 }]
var additionalusers = [ { name: "jacob", age: 25 }, { name: "bill" , age: 25 }]
var newusers = jLinq.from(users).except(additionalusers).select();
>>> newusers = [ { name: "bob" , age: 30 } ]
It's a bit overkill for you at the moment, but it's a robust solution that I was glad to learn about.
It can do intersects, unions, handle boolean logic and all kinds of great linq style goodness.

Categories

Resources