Finding the index of an object within an array efficiently - javascript

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

Related

How to remove duplicate names from array of objects

var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
I have tried the following:
function removeDuplicates:(dataObject){
self.dataObjectArr = Object.keys(dataObject).map(function(key){
return dataObject[key];
});
for(var i= 0; i < self.dataObjectArr.length; i++ ){
self.dataObjectArr[i]['name'] = self.dataObjectArr[i];
self.uniqArr = new Array();
for(var key in self.dataObjectArr){
self.uniqArr.push(self.dataObjectArr[key]);
}
}
self.uniqObject = DataMixin.toObject(self.uniqArr);
return self.uniqObject;
}
But I get error saying: Uncaught TypeError: Converting circular structure to JSON.
You should push the name to an array or a set and check the same in the following..
var arr = [{
level: 0,
name: "greg"
}, {
level: 0,
name: "Math"
}, {
level: 0,
name: "greg"
}]
function removeDuplicates(arr) {
var temp = []
return arr.filter(function(el) {
if (temp.indexOf(el.name) < 0) {
temp.push(el.name)
return true
}
})
}
console.log(removeDuplicates(arr))
Here's a generic "uniquify" function:
function uniqBy(a, key) {
var seen = new Set();
return a.filter(item => {
var k = key(item);
return !seen.has(k) && seen.add(k)
});
}
///
var arr = [
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:"joe"},
{level:0,name:Math},
{level:0,name:"greg"},
{level:0,name:"greg"},
{level:0,name:Math},
{level:0,name:"greg"}
];
uniq = uniqBy(arr, x => x.name);
console.log(uniq);
See here for the in-depth discussion.
I believe you have a syntax error " removeDuplicates:(dataObject){ ..."
should be without the ":" >> " removeDuplicates(dataObject){ ... "
"
You can try this :
function removeDuplicates(arr){
var match={}, newArr=[];
for(var i in arr){ if(!match[arr[i].name]){ match[arr[i].name]=1; var newArr=i; } }
return newArr;
}
arr = removeDuplicates(arr);
You can use $.unique(), $.map(), $.grep()
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var res = $.map($.unique($.map(arr, el => el.name)), name =>
$.grep(arr, el => el.name === name)[0]);
jsfiddle https://jsfiddle.net/4tex8xhy/3
Or you can use such libraries as underscore or lodash (https://lodash.com/docs/4.16.2). Lodash example:
var arr = [
{level:0,name:"greg"},
{level:0,name:"Math"},
{level:0,name:"greg"}
];
var result = _.map(_.keyBy(arr,'name'));
//result will contain
//[
// {
// "level": 0,
// "name": "greg"
// },
// {
// "level": 0,
// "name": "Math"
// }
//]
Ofc. one thing to always consider in these tasks, what do you want exactly are you going to do: modify an existing array, or get a new one back. This example returns you a new array.

count the occurrence of a string from a value in array of objects

I have an array of objects like this:
[
{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
},
{
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
},
{
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}
]
I need the result array to be like this:
{
"x": {
"response": {
"Cost": 2,
"Taste": 3,
"Other": 2
}
},
"y_a": {
"response": {
"Spillover": 2,
"Packaging": 1
}
}
}
Also, I have an array of parameters
['x', 'y_a', 'z']
Here there could many more parameters like x, y. the last string issue remains constant in every parameter. And it is grouped by the occurrence.
Cost has been entered twice, Taste entered thrice.
How can I do that in javascript? I am using lodash.
This is what I was trying:
Here data is the array of object which is a mongodb object. And parameters is the array of parameters that I mentioned above.
let obj = {};
_.forEach(data, (v, k) => {
obj.parameters = [];
_.forIn(v.toJSON(), (val, key) => {
// let count = 0;
var bucket = _.find(parameters, k => _.startsWith(key, k));
if (bucket) {
if (key === `${bucket}_issue`) {
obj[bucket] = obj[bucket] || {};
obj[bucket]["response"] = obj[bucket]["response"] || {};
obj[bucket]["response"]["all"] = obj[bucket]["response"]["all"] || [];
obj[bucket]["response"]["all"].push(_.words(val));
}
}
});
});
In pure javascript you could do it like this using forEach() loop
var data = [{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
}, {
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
}, {
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}]
var o = {}
data.forEach(function(e) {
Object.keys(e).forEach(function(k) {
var p = e[k].split(', ');
var re = /\_(?:.(?!\_))+$/
var key = k.split(re)[0];
if (!o[key]) o[key] = {response: {}};
p.forEach(function(a) {
o[key].response[a] = (o[key].response[a] || 0) + 1;
})
})
})
document.body.innerHTML = '<pre>' + JSON.stringify(o, 0, 4) + '</pre>';
You can use _.mergeWith() with a customizer function to achieve the merge you want, and then loop the result with _.transform() to remove the `_issue from each key:
var arr = [{
x_issue: 'Cost, Taste, Other',
y_a_issue: 'Spillover'
}, {
x_issue: 'Cost, Taste',
y_a_issue: 'Spillover'
}, {
x_issue: 'Taste, Other',
y_a_issue: 'Packaging'
}];
/** Create the mergeWithResponse method **/
var mergeWithResponse = _.partialRight(_.mergeWith, function(ov, sv) {
var oValue = ov ? ov : { // if the original value is undefined initialize it with a response property
response: {}
};
return sv.split(',').reduce(function(final, word) { // split the words of the source value and iterate them
var w = word.trim(); // remove space before and after the words
final.response[w] = (final.response[w] || 0) + 1; // add the word to the response and / or increment the counter
return final; // return the final value with the response object
}, oValue);
});
var result = _(mergeWithResponse.apply(_, [{}].concat(arr))) // add an empty object to the beginning of the array, and apply the new array as paramaters for mergeWithResponse
.transform(function(result, value, key) { // remove the "_issue" string an from each key, and create an object with the new keys
var k = key.replace('_issue', '');
result[k] = value;
});
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.14.2/lodash.min.js"></script>

JavaScript - find unique values in array of objects, with count and create a new array of objects

I have an array of objects as below:
var arr =[
{
price:20,
rule:a
},
{
price:10,
rule:b
},
{
price:5,
rule:a
},
{
price:50,
rule:b
},
{
price:70,
rule:b
}
]
I want to extract an array of objects out of this as below:
var filteredArr = [
{
rule:a,
countOfRule:2,
minPriceForThisRule:5
},
{
rule:b,
countOfRule:3,
minPriceForThisRule:10
}
]
This means:
1) I want to create new array with no. of objects as unique no. of rules in first array "arr"
2) Need to count the unique rule repetition and add as property in new array objects - given as "countOfRule"
3) Find the minimum price for in a category of unique rule - given as "minPriceForThisRule"
I have read similar answers on SO and was able to get first 2 conditions only, and that too were not in the format as i need.
What I tried, referring to links on SO:
var ff = {},e;
for (var i = 0,l=arr.length; i < l; i++) {
e = arr[i];
ff[e.rule] = (ff[e.rule] || 0) + 1;
}
But this gives only a single object as
{
a : 2,
b: 3
}
You can do this with forEach and thisArg optional parameter.
var arr = [{"price":20,"rule":"a"},{"price":10,"rule":"b"},{"price":5,"rule":"a"},{"price":50,"rule":"b"},{"price":70,"rule":"b"}],
r = [];
arr.forEach(function(e) {
if(!this[e.rule]) {
this[e.rule] = {rule: e.rule, countOfRule: 0, minPriceForThisRule: e.price}
r.push(this[e.rule]);
}
this[e.rule].countOfRule++;
if(this[e.rule].minPriceForThisRule > e.price) this[e.rule].minPriceForThisRule = e.price;
}, {});
console.log(r)
I would use reduce. Something like:
var reduced = arr.reduce(function(memo, obj){
var rule = memo[obj.rule];
rule.countOfRule++;
if(!rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
} else{
if(obj.price < rule.minPriceForThisRule){
rule.minPriceForThisRule = obj.price;
}
}
return memo;
}, map);
where the initial map looks like:
var map = {
1: {
rule: 1,
countOfRule: 0,
minPriceForThisRule: null
},
2: {
rule: 2,
countOfRule: 0,
minPriceForThisRule: null
}
}
Of course you could dynamically create the map if needed.
https://plnkr.co/edit/wLw3tEx2SMXmYE7yOEHg?p=preview
This is how i would do this job,
var arr =[
{
price:20,
rule:"a"
},
{
price:10,
rule:"b"
},
{
price:5,
rule:"a"
},
{
price:50,
rule:"b"
},
{
price:70,
rule:"b"
}
],
reduced = arr.reduce((p,c) => { var fo = p.find(f => f.rule == c.rule);
fo ? (fo.countOfRule++,
fo.minPriceForThisRule > c.price && (fo.minPriceForThisRule = c.price))
: p.push({rule:c.rule, countOfRule:1, minPriceForThisRule: c.price});
return p;
},[]);
console.log(reduced);
Arrows might not work at IE or Safari. If that would be a problem please replace them with their conventional counterparts.

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

sort by number of occurrence(count) in Javascript array

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

Categories

Resources