Is there a better way to do this?
I'm storing values in what some would erroneously call an associated array:
The tokens object stores.... tokens and a count of documents using that token on a per-db level.
var tokens = {'db1' : { '654321': { 'docCount': 1 },
'321456': { 'docCount': 2 } },
'db2' : { '999999': { 'docCount': 1 } } };
I can add/remove dbs and tokens and update the docCounts appropriately.
We can assume, due to code omitted for brevity, that if a db exists, a token also exists with a docCount of at least 1.
If a db exists and I need to retrieve ANY of its tokens, what is the best method?
If the dbs held arrays, it would be as easy as tokens['db1'][0]... but I'm not using arrays.
I have something like the following, "inspired" by LINQ (please don't blame LINQ):
// NOTE: default not implemented here
var firstOrDefault = function(obj) {
var thing;
for (var i in obj) {
thing = i;
break;
}
return thing;
};
which would be called as so (simplified for example):
var anyToken;
if (tokens['db1') { anyToken = firstOrDefault(tokens['db1']); }
Generally returning per the above example '654321' (as this is an object, not an array, order is not guaranteed, but either value is acceptable in my code).
Is this a reasonable method to get any value?
Is there a better method?
Should I just suck it up, shove everything into an array, and wrap the storage features that way?
UPDATE: I've removed the default reference, as an unfound item will a perfectly acceptable undefined response:
// NOTE: obj.hasOwnProperty not implemented for brevity
var firstOrAny = function(obj) {
var thing;
for (var i in obj) {
thing = i;
break;
}
return thing;
};
which would be called as so (simplified for example):
var anyToken;
if (tokens['db1') { anyToken = firstOrAny(tokens['db1']); }
Slightly shorter solution:
var firstOrDefault = function(obj, d) {
for (var i in obj)
{
if (obj.hasOwnProperty(i))
{
return obj[i];
}
}
return d;
};
But yes, it is the fastest way to get any (usually first inserted) key from an object.
I also added a hasOwnProperty check to prevent cases where the values are retrieved from the prototype chain.
Related
I have the following script in Google Apps Script:
for(var i=0; i<lastCode; i++) {
var productCode = prodCodesArr[i];
for(var j=0; j<kelliLastCode; j++) {
var kelliProductCode = kelliCodesArr[j];
if(productCode == kelliProductCode) {
Logger.log('match found')
}
}
}
The 2 arrays are created dynamically. So the idea is (and I know there must be MUCH better ways to do this, but I am pretty new to this so bear with me) that I am setting i to the value of the first product code in one array and then looping through the other array whilst storing the product codes in this one to j. Now, I tried logging:
Logger.log(productCode + ' - ' + kelliProductCode);
And this worked and indeed, there were instances where productCode and kelliProduct code matched.
Yet my if statement above does not pick these up.
Again, I'm sure I've botched this entirely but any help would be greatly appreciated...
What's the point of the check? To determine which of your prodCodesArr items are also in kelliCodesArr? Why not parse kelliCodesArr just once, and then use hash lookups instead of array traversal? This will mean that you don't have to use nested for loops, which will scale very poorly as the inner loop size grows. An example (with some checks for assumptions on my part):
function foo() {
const kelliCodes = getKelliCodesArraySomehow();
const productCodes = getProductCodesArraySomehow();
// If these are 2D arrays, note that for `var a = ['help']; var b = ['help'];`
// `a` is never equal to `b` because they are not the exact same object in memory.
if (kelliCodes.length && Array.isArray(kelliCodes[0])) {
throw new TypeError("This SO answer was predicated on `kelliCodes` and `productCodes` being 1D arrays, but they aren't!");
}
const kelliLookup = kelliCodes.reduce(function (obj, kpc, idx) {
if (typeof kpc === 'object') {
console.log({message: "This SO answer assumed kpc was a string", kpc: kpc});
throw new TypeError("You probably want to store a property of this object, not the whole object");
}
obj[kpc] = idx;
return obj;
}, {});
var productsAlsoInKelliCodes = productCodes.filter(function (pc) {
return kelliLookup.hasOwnProperty(pc);
});
productsAlsoInKelliCodes.forEach(function (pc) {
Logger.log("The index of this product code %s in kelliCodes is %s", pc, kelliLookup[pc]);
});
}
If your ___codes arrays are 2D arrays, you should flatten them before comparison, as comparing an Array instance to another Array instance will always return false, even if they contain the same element primitives--they aren't the exact same Array instance:
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
Array#forEach
Array#map
In JS, which is faster: Object's "in" operator or Array's indexof?
Javascript: what lookup is faster: array.indexOf vs object hash?
I'm sure there are more.
Something like this might help you to see what's happening:
function compareA(prodCodesArr,kelliCodesArr) {
var html="";
for(var i=0;i<prodCodesArr.length;i++) {
for(var j=0;j<kelliCodesArr.length;j++) {
if(productCodesArr[i]==kelliCodesArr[j]) {
html+=Utilities.formatString('Matched: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}else{
html+=Utilities.formatString('No-Match: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}
}
}
var userInterface=HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Comparing')
}
In a react app, I have state that looks like this:
dates: {"2015-11-20":{
"13:00":{...},
"14:00":{...}},
"2015-11-21":{
"18:00":{...},
"19:00":{...}}}
I'm running into a problem with updating state - my use case is I want users to be able to copy all the times (e.g. 1pm & 2pm above) to other dates via the UI and have state update accordingly.
So far, all my attempts result in referencing, where the separate objects become linked and any update to one results in an update to the other.
Sample code that produces the broken behaviour:
copyTimes: function() {
var keys = [];
Object.keys(this.state.dates).map(function(key) {
keys.push(key);
});
for(var i in keys) {
this.state.dates[keys[i]] = this.state.dates[this.state.selectedDate];
}
this.setState({dates: this.state.dates});
}
I believe I might need to use the react add on 'update' to solve this, as per https://facebook.github.io/react/docs/update.html
but I can't fully understand the syntax approach I need to take. How do I cause a correct update to 'copy' the contents of one date: '2015-11-20' to another e.g. 2015-11-21 in the above example?
EDIT
As per (pointed out in comments): What is the most efficient way to deep clone an object in JavaScript?
copyTimes: function() {
var keys = [];
var tempStart = JSON.parse(JSON.stringify(this.state.dates));
Object.keys(this.state.dates).map(function(key) {
keys.push(key);
});
for(var i in keys) {
tempStart[keys[i]] = JSON.parse(JSON.stringify(this.state.dates[this.state.selectedDate]));
}
this.setState({dates: tempStart});
},
The above works - but is extremely ugly. Is there a better approach?
copyTimes: function() {
var keys = [];
Object.keys(this.state.dates).map(function(key) {
keys.push(key);
});
var newDates = Object.assign({}, this.state.dates);;
for(var i in keys) {
newDates[keys[i]] = this.state.dates[this.state.selectedDate];
}
this.setState({dates: newDates});
}
I'm looking for a way (in JavaScript) to collect a set of objects into multiple arrays, where each array contains a certain type of object, and the arrays are stored as values in an associative array, with the keys being the types. For example:
Input:
[<apple>, <cat>, <pear>, <mercedes>, <dog>, <ford>, <orange>]
Output:
{
'fruit': [<apple>, <pear>, <orange>],
'animal': [<cat>, <dog>],
'car': [<mercedes>, <ford>]
}
In ruby, you could do the following:
things_by_type = {}
things.each do |thing|
(things_by_type[thing.type] ||= []) << thing
end
which is nice and concise.
What's a good pattern for doing the same thing in JavaScript that's concise and efficient? I could do something like this, but it's not as nice:
var thing, things_by_type = {};
for (var i = 0; i < things.length; i++) {
thing = things[i];
if(things_by_type[thing.type]) {
things_by_type[thing.type].push(thing);
} else {
things_by_type[thing.type] = [thing];
}
}
I'm not sure if it's a good pattern, but it's similar to your ruby sample:
var things_by_type = {};
for (var i in things) {
var thing = things[i];
(things_by_type[thing.type] || (things_by_type[thing.type] = [])).push(thing);
}
And if you can assume Javascript 1.6:
var things_by_type = {};
things.forEach(function(thing) {
(things_by_type[thing.type] || (things_by_type[thing.type] = [])).push(thing);
})
In ruby, you could do the following:
things_by_type = {}
things.each do |thing|
(things_by_type[thing.type] ||= []) << thing
end
which is nice and concise.
Actually, you can make that even nicer.
First off, Hash.new takes a block argument which will be called every time a non-existing key is referenced. You can use that to create that key. That way you get rid of the conditional logic inside the block.
things_by_type = Hash.new {|h, k| h[k] = [] }
things.each do |thing|
things_by_type[thing.type] << thing
end
Secondly, what you have here is called a fold or reduce: you are "folding" or "reducing" a collection (the array of objects) into a single value (the hash, which confusingly also happens to be a collection, but is nonetheless a single value).
You can generally easily spot this pattern by looking for places where you initialize some variable, then loop over a collection and manipulate that variable at every iteration of the loop.
Ruby has folding built in, via the Enumerable#reduce method:
things.reduce(Hash.new {|h, k| h[k] = [] }) do |h, thing|
h.tap { h[thing.type] << thing }
end
But what you are really doing, is grouping the array by the type attribute of its elements, which is also built into Ruby as Enumerable#group_by:
things.group_by {|thing| thing.type }
Which can be further simplified by using Symbol#to_proc to
things.group_by(&:type)
Unfortunately, ECMAScript doesn't have groupBy, nor default values for non-existing properties, but it does have Array.prototype.reduce:
things.reduce(function (acc, thing) {
(acc[thing.type] || (acc[thing.type] = [thing])).push(thing);
return acc;
}, {});
almost the same code, but works a bit different, you can use the fancy set function easier and it separates logic:
var a = {set:function(type,thing){
if (this[type]) {
this[type].push(thing);
} else {
this[type] = [thing];
}
}};
a.set('a',0);
a.set('b',1);
a.set('a',2);
I've had trouble with people jumping to conclusions about what I need here, so please read this and think about it before answering.
Here is the case:
You have an incoming object. You do not know the structure of this object. You do however have a "target" to something in the object. So let's pretend there is myObject, and you have some kind of target defined like an array of association levels:
var objectTarget = [ 'firstLevel', 'secondLevel' ,'targetProperty' ];
Now the incoming myObject looks like this:
{
firstLevel: {
secondLevel: {
targetProperty: "The goods!"
}
}
}
But as stated before, you don't know the structure. All you know is what is in the objectTarget array.
My problem is being able to address an arbitrary location within an object based solely off a target. If I knew that the target would always be three levels deep, then I could simply reference it like this:
myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]];
However, because I cannot be sure of the number of level depth, this is not adequate. The only way I have been able to accomplish this task is choose a maximum number of reasonable levels, and then switch on it. Like so:
switch ( objectTarget.length) {
case 1:
var result = myObject[objectTarget[1]];
break;
case 2:
var result = myObject[objectTarget[1]][objectTarget[2]];
break;
case 3:
var result = myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]];
break;
case 4:
var result = myObject[objectTarget[1]][objectTarget[2]][objectTarget[3]][objectTarget[1]];
break;
}
..etc
This is obviously extremely messy, and not the optimal solution.
Does this properly explain my problem? Is there a cleaner manner in which to accomplish this?
Thank you in advance for any advice you can provide.
An attempt off the top of my head:
function findTarget(obj, targets) {
for(var i = 0; i < targets.length; i++) {
var prop = targets[i];
if(obj[prop] != undefined) {
obj = obj[prop];
} else {
return undefined; // Whatever you want when the target does not exist
// or, if it's useful to you
return obj; // Maximum reachable target
}
}
return obj;
}
var target = findTarget(incoming, ['level1', 'level2', ...]);
if(target == undefined) {
// couldn't traverse the entire target list...
}
Another approach if you can use (or include for IE) the reduce Array method:
function getTarget(obj, target) {
return target.reduce(function(a, b) { return a && a[b]; }, obj);
}
// usage (assuming that `myObject` is your sample object):
var target = ['firstLevel', 'secondLevel' ,'targetProperty'];
getTarget(myObject, target); // "The goods!"
I need to find which id numbers are missing inside s.data compared to users.
Is there a better(smaller code) way to compare?
Thanks ;)
if(users.length != undefined)
{
for(y=0;y<users.length;y++)
{
var left = true;
for(y2=0;y2<s.data.length;y2++)
{
if(users[y].client_id==s.data[y2].client_id) {left = false;break;}
}
if(left) {users[y].ref.remove();delete users[y];}
}
}
else if(!jQuery.isEmptyObject(users))
{
var left = true;
for(y2=0;y2<s.data.length;y2++)
{
if(users.client_id==s.data[y2].client_id) {left = false;break;}
}
if(left) {users.ref.remove();users = {};}
}
Haven't checked if this is working code. :)
First, off, the 2nd branch appears to be nothing but a specialization of the first branch. You can use this to either make the "2nd" users = [users] (in which case users really means users and not a-user) and eliminates the top branch entirely, or remove the the logic into a function invoked per-user.
Now, to tackle the inner loop: What this is a 'map' and a 'contains'. Looking at it just in terms of a contains:
// Returns true if any item in data.client_id (an array)
// is that of user.client_id
function dataContains (user, data) {
for (var i = 0; i < data.length; i++) {
if (data[i].client_id == user.client_id) {
return true
}
}
return false
}
Now the code is reduced to:
for (each user) {
if (!dataContains(user, data)) {
// do something here
}
}
However, we could go one step further and use a generic 'contains' if we also have a 'map'. The final form is then:
var dataIds = map(data, function (x) { return x.client_id })
for (each user) {
if (!contains(user.client_id, dataIds)) {
..
}
}
Where the 'contains' is much more generalized:
// Returns true iff item is contained within arr
function contains (item, arr) {
// Just do what the comment documentation says
}
If you are using jQuery you already have handy functions:
'contains' - inArray, and a "sorta" 'map' - map. However, be warned! The jQuery 'map' is really a flat-map and was given an incorrect name and incomplete documentation!
I believe ECMAScript ED5 has these functions standard.
Also, you could invert the client_id's in the data to object keys and simply test for key existence, which is O(1) vs. O(n) iff the look-up is built once (or at least much, much less than it's used) and so it may be "theoretically" better. The size of n makes a large difference if it will actually matter, if at all. In this case it's likely the look-up could be built incrementally and saved between times this code is executed.
var existingIds = {}
for (var i = 0; i < data.length; i++) {
existingIds[data[i].client_id] = true
}
for (each user) {
if (!existingIds[user.client_id]) {
..
}
}