Related
I have the following function which takes some values that the user has entered and creates JavaScript objects from those values, then puts those objects in an array and returns the array:
function createObjects() {
var objectArray = [];
for (var i = 0; i < someCount; i++) {
var object = {
property1: someProperty1,
property2: someProperty2,
property3: someProperty3,
property4: someProperty4
};
objectArray.push(object);
}
return objectArray;
}
Now, I want to compare these objects' properties and determine whether any two contain all of the same values for property1, property2, property3, and property4. If any two of these objects have all four of the same values for these properties, I want a validation check to return false. Here is what I have so far:
function objectsAreUnique() {
var objects = createObjects();
for(var i = 0; i < objects.length; i++) {
//need to determine whether all four of the properties are the same for any two objects
//if(objectsAreSame) { return false; }
}
return true;
}
I have a few ideas, but I'm interested to see what is the most efficient way to achieve this. Thanks!
If you can guarantee that the properties will always be inserted in the same order (which will be the case if using an object literal as in your example), you can do this in ~O(n) using JSON.stringify and a Set:
function objectsAreUnique() {
const objects = createObjects();
return (new Set(objects.map(o => JSON.stringify(o)))).size == objects.length;
}
First, create a function to test whether two objects are the same. You can enter each property individually, or get creative with the JSON.stringify function
properties individually:
function objectsIdentical(obj1, obj2) {
return obj1.property1 == obj2.property1 && obj1.property2 == obj2.property2 && obj1.property3 == obj2.property3 && obj1.property4 == obj2.property4;
}
JSON.stringify (recommended for objects with many properties)
function objectsIdentical(obj1, obj2) {
return JSON.stringify(obj1).replace(/^.|.$/g, "").split(",").sort().join(",") == JSON.stringify(obj2).replace(/^.|.$/g, "").split(",").sort().join(",");
}
Then, you can use a for loop to check if any of them are identical.
for (let i=0; i<objects.length-1; i++) {
for (let j=i+1; j<objects.length; j++) {
if (objectsIdentical(objects[i], objects[j])) {
return false;
}
}
}
return true;
If you're familiar with the "some" function, you can use that.
return !objects.some((v, i) => objects.slice(i+1).some(w => objectsIdentical(v, w)))
function objectsAreUnique() {
var objects = createObjects();
var stringifiedAndSorted = objects.map(obj => JSON.stringify(obj)).sort()
for(var i = 0; i < stringifiedAndSorted.length-1; i++) {
if(i === i+1)
return false;
}
return true;
}
Let's say I have a Javascript associative array (a.k.a. hash, a.k.a. dictionary):
var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;
How can I iterate over the keys in sorted order? If it helps simplify things, I don't even need the values (they're all just the number 1).
You can use the Object.keys built-in method:
var sorted_keys = Object.keys(a).sort()
(Note: this does not work in very old browsers not supporting EcmaScript5, notably IE6, 7 and 8. For detailed up-to-date statistics, see this table)
You cannot iterate over them directly, but you can find all the keys and then just sort them.
var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;
function keys(obj)
{
var keys = [];
for(var key in obj)
{
if(obj.hasOwnProperty(key))
{
keys.push(key);
}
}
return keys;
}
keys(a).sort(); // ["a", "b", "z"]
However there is no need to make the variable 'a' an array. You are really just using it as an object and should create it like this:
var a = {};
a["key"] = "value";
you could even prototype it onto object:
Object.prototype.iterateSorted = function(worker)
{
var keys = [];
for (var key in this)
{
if (this.hasOwnProperty(key))
keys.push(key);
}
keys.sort();
for (var i = 0; i < keys.length; i++)
{
worker(this[ keys[i] ]);
}
}
and the usage:
var myObj = { a:1, b:2 };
myObj.iterateSorted(function(value)
{
alert(value);
}
I agree with Swingley's answer, and I think it is an important point a lot of these more elaborate solutions are missing. If you are only concerned with the keys in the associative array and all the values are '1', then simply store the 'keys' as values in an array.
Instead of:
var a = { b:1, z:1, a:1 };
// relatively elaborate code to retrieve the keys and sort them
Use:
var a = [ 'b', 'z', 'a' ];
alert(a.sort());
The one drawback to this is that you can not determine whether a specific key is set as easily. See this answer to javascript function inArray for an answer to that problem. One issue with the solution presented is that a.hasValue('key') is going to be slightly slower than a['key']. That may or may not matter in your code.
There's no concise way to directly manipulate the "keys" of a Javascript object. It's not really designed for that. Do you have the freedom to put your data in something better than a regular object (or an Array, as your sample code suggests)?
If so, and if your question could be rephrased as "What dictionary-like object should I use if I want to iterate over the keys in sorted order?" then you might develop an object like this:
var a = {
keys : new Array(),
hash : new Object(),
set : function(key, value) {
if (typeof(this.hash[key]) == "undefined") { this.keys.push(key); }
this.hash[key] = value;
},
get : function(key) {
return this.hash[key];
},
getSortedKeys : function() {
this.keys.sort();
return this.keys;
}
};
// sample use
a.set('b',1);
a.set('z',1);
a.set('a',1);
var sortedKeys = a.getSortedKeys();
for (var i in sortedKeys) { print(sortedKeys[i]); }
If you have no control over the fact that the data is in a regular object, this utility would convert the regular object to your fully-functional dictionary:
a.importObject = function(object) {
for (var i in object) { this.set(i, object); }
};
This was a object definition (instead of a reusable constructor function) for simplicity; edit at will.
Get the keys in the first for loop, sort it, use the sorted result in the 2nd for loop.
var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;
var b = [];
for (k in a) b.push(k);
b.sort();
for (var i = 0; i < b.length; ++i) alert(b[i]);
You can use the keys function from the underscore.js library to get the keys, then the sort() array method to sort them:
var sortedKeys = _.keys(dict).sort();
The keys function in the underscore's source code:
// Retrieve the names of an object's properties.
// Delegates to **ECMAScript 5**'s native `Object.keys`
_.keys = nativeKeys || function(obj) {
if (obj !== Object(obj)) throw new TypeError('Invalid object');
var keys = [];
for (var key in obj) if (_.has(obj, key)) keys.push(key);
return keys;
};
// Shortcut function for checking if an object has a given property directly
// on itself (in other words, not on a prototype).
_.has = function(obj, key) {
return hasOwnProperty.call(obj, key);
};
<script type="text/javascript">
var a = {
b:1,
z:1,
a:1
}; // your JS Object
var keys = [];
for (key in a) {
keys.push(key);
}
keys.sort();
var i = 0;
var keyslen = keys.length;
var str = '';
//SORTED KEY ITERATION
while (i < keyslen) {
str += keys[i] + '=>' + a[keys[i]] + '\n';
++i;
}
alert(str);
/*RESULT:
a=>1
b=>1
z=>1
*/
</script>
var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;
var keys=Object.keys(a).sort();
for(var i=0,key=keys[0];i<keys.length;key=keys[++i]){
document.write(key+' : '+a[key]+'<br>');
}
I really like #luke-schafer's prototype idea, but also hear what he is saying about the issues with prototypes. What about using a simple function?
function sortKeysAndDo( obj, worker ) {
var keys = Object.keys(obj);
keys.sort();
for (var i = 0; i < keys.length; i++) {
worker(keys[i], obj[keys[i]]);
}
}
function show( key, value ) {
document.write( key + ' : ' + value +'<br>' );
}
var a = new Array();
a['b'] = 1;
a['z'] = 1;
a['a'] = 1;
sortKeysAndDo( a, show);
var my_object = { 'c': 3, 'a': 1, 'b': 2 };
sortKeysAndDo( my_object, show);
This seems to eliminate the issues with prototypes and still provide a sorted iterator for objects. I am not really a JavaScript guru, though, so I'd love to know if this solution has hidden flaws I missed.
I am wondering how I can check if a duplicate pair of values in an array exist as part of a larger array in javascript. You can see there is a duplicate pair of [1,2] - so the function should just return true. i.e
var arr = [[1,2], [3,4], [5,6], [7,8], [9,10], [11,12], [13,14], [1,2]]
I have tried using this logic which gives me a clean array and a "true"
var unique = [];
var done = []; var dup = false;
for(var x = 0; x < arr.length; x++) {
var myStr = arr[x].toString();
if(done.indexOf(myStr) != -1) {
// val already exist, ignore
dup = true;
continue;
}
done.push(myStr);
unique.push(arr[x]);
}
But I was wondering if there is something more elegant using Underscore ?
The shortest way would be to use _.uniq and JSON.stringify:
function unique(arr) {
return _.uniq(arr, JSON.stringify).length === arr.length;
}
But that doesn't short-circuit, so it's somewhat slow compared to the other ways you could do it. Tomalak's second function should be faster.
Well, uniq seems like a good fit
function containsDuplicates(arr) {
return arr.length !== _.uniq(arr, function (item) { return item.toString(); }).length;
}
You should use Blender's version of this function. It's shorter and safer.
BTW, your code should look more like this:
function containsDuplicates(arr) {
var index = {}, i, str;
for(i = 0; i < arr.length; i++) {
// you could use arr[i].toString() here, but JSON.stringify()
// is a lot safer because it cannot create ambiguous output.
str = JSON.stringify(arr[i]);
if (index.hasOwnProperty(str)) {
return true;
} else {
index[str] = true;
}
}
return false;
}
Note that this is probably more efficient than the underscore one-liner.
Although stringify is the answer most of the time, it still has its issues, for example {"x":1,"y":2} and {"y":2,"x":1} are considered different. If you need a 100% accurate comparison, there's no other way as to store already processed objects and deep compare them (luckily, underscore provides an utility for this).
uniq2 = function(xs) {
return _.reduce(xs, function(result, x) {
if(!_.any(result, _.partial(_.isEqual, x)))
result.push(x);
return result;
}, []);
}
Test:
var arr = [[1,2], [3,4], "1,2", "[1,2]", [1,2], {x:1,y:2}, {y:2,x:1}]
console.log(uniq2(arr))
// [[1,2],[3,4],"1,2","[1,2]",{"x":1,"y":2}]
This is going to be quadratic in the worst case, but there's no other way.
I have an array like this:
[{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"},...]
How can I get the index of the object that matches a condition, without iterating over the entire array?
For instance, given prop2=="yutu", I want to get index 1.
I saw .indexOf() but think it's used for simple arrays like ["a1","a2",...]. I also checked $.grep() but this returns objects, not the index.
As of 2016, you're supposed to use Array.findIndex (an ES2015/ES6 standard) for this:
a = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}];
index = a.findIndex(x => x.prop2 ==="yutu");
console.log(index);
It's supported in Google Chrome, Firefox and Edge. For Internet Explorer, there's a polyfill on the linked page.
Performance note
Function calls are expensive, therefore with really big arrays a simple loop will perform much better than findIndex:
let test = [];
for (let i = 0; i < 1e6; i++)
test.push({prop: i});
let search = test.length - 1;
let count = 100;
console.time('findIndex/predefined function');
let fn = obj => obj.prop === search;
for (let i = 0; i < count; i++)
test.findIndex(fn);
console.timeEnd('findIndex/predefined function');
console.time('findIndex/dynamic function');
for (let i = 0; i < count; i++)
test.findIndex(obj => obj.prop === search);
console.timeEnd('findIndex/dynamic function');
console.time('loop');
for (let i = 0; i < count; i++) {
for (let index = 0; index < test.length; index++) {
if (test[index].prop === search) {
break;
}
}
}
console.timeEnd('loop');
As with most optimizations, this should be applied with care and only when actually needed.
How can I get the index of the object tha match a condition (without iterate along the array)?
You cannot, something has to iterate through the array (at least once).
If the condition changes a lot, then you'll have to loop through and look at the objects therein to see if they match the condition. However, on a system with ES5 features (or if you install a shim), that iteration can be done fairly concisely:
var index;
yourArray.some(function(entry, i) {
if (entry.prop2 == "yutu") {
index = i;
return true;
}
});
That uses the new(ish) Array#some function, which loops through the entries in the array until the function you give it returns true. The function I've given it saves the index of the matching entry, then returns true to stop the iteration.
Or of course, just use a for loop. Your various iteration options are covered in this other answer.
But if you're always going to be using the same property for this lookup, and if the property values are unique, you can loop just once and create an object to map them:
var prop2map = {};
yourArray.forEach(function(entry) {
prop2map[entry.prop2] = entry;
});
(Or, again, you could use a for loop or any of your other options.)
Then if you need to find the entry with prop2 = "yutu", you can do this:
var entry = prop2map["yutu"];
I call this "cross-indexing" the array. Naturally, if you remove or add entries (or change their prop2 values), you need to update your mapping object as well.
What TJ Crowder said, everyway will have some kind of hidden iteration, with lodash this becomes:
var index = _.findIndex(array, {prop2: 'yutu'})
var CarId = 23;
//x.VehicleId property to match in the object array
var carIndex = CarsList.map(function (x) { return x.VehicleId; }).indexOf(CarId);
And for basic array numbers you can also do this:
var numberList = [100,200,300,400,500];
var index = numberList.indexOf(200); // 1
You will get -1 if it cannot find a value in the array.
var index;
yourArray.some(function (elem, i) {
return elem.prop2 === 'yutu' ? (index = i, true) : false;
});
Iterate over all elements of array.
It returns either the index and true or false if the condition does not match.
Important is the explicit return value of true (or a value which boolean result is true). The single assignment is not sufficient, because of a possible index with 0 (Boolean(0) === false), which would not result an error but disables the break of the iteration.
Edit
An even shorter version of the above:
yourArray.some(function (elem, i) {
return elem.prop2 === 'yutu' && ~(index = i);
});
Using Array.map() and Array.indexOf(string)
const arr = [{
prop1: "abc",
prop2: "qwe"
}, {
prop1: "bnmb",
prop2: "yutu"
}, {
prop1: "zxvz",
prop2: "qwrq"
}]
const index = arr.map(i => i.prop2).indexOf("yutu");
console.log(index);
The best & fastest way to do this is:
const products = [
{ prop1: 'telephone', prop2: 996 },
{ prop1: 'computadora', prop2: 1999 },
{ prop1: 'bicicleta', prop2: 995 },
];
const index = products.findIndex(el => el.prop2 > 1000);
console.log(index); // 1
I have seen many solutions in the above.
Here I am using map function to find the index of the search text in an array object.
I am going to explain my answer with using students data.
step 1: create array object for the students(optional you can create your own array object).
var students = [{name:"Rambabu",htno:"1245"},{name:"Divya",htno:"1246"},{name:"poojitha",htno:"1247"},{name:"magitha",htno:"1248"}];
step 2: Create variable to search text
var studentNameToSearch = "Divya";
step 3: Create variable to store matched index(here we use map function to iterate).
var matchedIndex = students.map(function (obj) { return obj.name; }).indexOf(studentNameToSearch);
var students = [{name:"Rambabu",htno:"1245"},{name:"Divya",htno:"1246"},{name:"poojitha",htno:"1247"},{name:"magitha",htno:"1248"}];
var studentNameToSearch = "Divya";
var matchedIndex = students.map(function (obj) { return obj.name; }).indexOf(studentNameToSearch);
console.log(matchedIndex);
alert("Your search name index in array is:"+matchedIndex)
You can use the Array.prototype.some() in the following way (as mentioned in the other answers):
https://jsfiddle.net/h1d69exj/2/
function findIndexInData(data, property, value) {
var result = -1;
data.some(function (item, i) {
if (item[property] === value) {
result = i;
return true;
}
});
return result;
}
var data = [{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"}]
alert(findIndexInData(data, 'prop2', "yutu")); // shows index of 1
function findIndexByKeyValue(_array, key, value) {
for (var i = 0; i < _array.length; i++) {
if (_array[i][key] == value) {
return i;
}
}
return -1;
}
var a = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}];
var index = findIndexByKeyValue(a, 'prop2', 'yutu');
console.log(index);
Try this code
var x = [{prop1:"abc",prop2:"qwe"},{prop1:"bnmb",prop2:"yutu"},{prop1:"zxvz",prop2:"qwrq"}]
let index = x.findIndex(x => x.prop1 === 'zxvz')
Another easy way is :
function getIndex(items) {
for (const [index, item] of items.entries()) {
if (item.prop2 === 'yutu') {
return index;
}
}
}
const myIndex = getIndex(myArray);
Georg have already mentioned ES6 have Array.findIndex for this.
And some other answers are workaround for ES5 using Array.some method.
One more elegant approach can be
var index;
for(index = yourArray.length; index-- > 0 && yourArray[index].prop2 !== "yutu";);
At the same time I will like to emphasize, Array.some may be implemented with binary or other efficient searching technique. So, it might perform better over for loop in some browser.
Why do you not want to iterate exactly ? The new Array.prototype.forEach are great for this purpose!
You can use a Binary Search Tree to find via a single method call if you want. This is a neat implementation of BTree and Red black Search tree in JS - https://github.com/vadimg/js_bintrees - but I'm not sure whether you can find the index at the same time.
One step using Array.reduce() - no jQuery
var items = [{id: 331}, {id: 220}, {id: 872}];
var searchIndexForId = 220;
var index = items.reduce(function(searchIndex, item, index){
if(item.id === searchIndexForId) {
console.log('found!');
searchIndex = index;
}
return searchIndex;
}, null);
will return null if index was not found.
var list = [
{prop1:"abc",prop2:"qwe"},
{prop1:"bnmb",prop2:"yutu"},
{prop1:"zxvz",prop2:"qwrq"}
];
var findProp = p => {
var index = -1;
$.each(list, (i, o) => {
if(o.prop2 == p) {
index = i;
return false; // break
}
});
return index; // -1 == not found, else == index
}
I am coding something in JS and I have to test code - I have to check if elements in 2 arrays are the same.
So I've got an array: boreholes = [[66000, 457000],[1111,2222]....]; and I want to check if this array contain element for eg. [66000,457000] so I did:
boreholes.indexOf([66000,457000]) but it returns -1, so I iterate trough array by:
for (var i = 0; i< boreholes.length; i++){
if (boreholes[i] == [66000, 457000]){
console.log('ok');
break;
}
};
but still I've got nothing. Can someone explain me what am I doing wrong?
You are comparing distinct objects. When comparing objects, the comparison only evaluates to true when the 2 objects being compared are the same object. I.E
var a = [1,2,3];
var b = a;
a === b //true
b = [1,2,3];
a === b //false, b is not the same object
To compare arrays like this, you need to compare all of their elements separately:
for (var i = 0; i < boreholes.length; i++) {
if (boreholes[i][0] == 66000 && boreholes[i][1] == 457000) {
console.log('ok');
break;
}
}
You cannot compare arrays like array1 == array2 in javascript like you're trying to do here.
Here is a kludge method to compare two arrays:
function isEqual(array1, array2){
return (array1.join('-') == array2.join('-'));
}
You can now use this method in your code like:
for (var i = 0; i< boreholes.length; i++){
if (isEqual(boreholes[i], [66000, 457000]){
console.log('ok');
break;
}
};
Currently I had the same problem, did it with the toString() method
var array1 = [1,2,3,[1,2,3]]
var array2 = [1,2,3,[1,2,3]]
array1 == array2 // false
array1.toString() == array2.toString() // true
var array3 = [1,2,3,[1,3,2]]
// Take attention
array1.toString() == array3.toString() // false
You could also doing it with the Underscore.js-framework for functional programming.
function containsElements(elements) {
_.find(boreholes, function(ele){ return _.isEqual(ele, elements); });
}
if(containsElements([66000, 457000])) {
console.log('ok');
}
The question isn't quite clear if there can be more than 2 elements in an array, so this might work
var boreholes = [[66000, 457000],[1111,2222]];
var it = [66000, 457000];
function hasIt(boreholes, check) {
var len = boreholes.length;
for (var a = 0; a < len; a++) {
if (boreholes[a][0] == check[0] && boreholes[a][1] == check[1]) {
// ok
return true;
}
}
return false;
}
if (hasIt(boreholes, it)) {
// ok, it has it
}