Javascript: using tuples as dictionary keys - javascript

I have a situation where I want to create a mapping from a tuple to an integer. In python, I would simply use a tuple (a,b) as the key to a dictionary,
Does Javascript have tuples? I found that (a,b) in javascript as an expression just returns b (the last item). Apparently this is inherited from C.
So, as a workaround, I thought I can use arrays instead,
my_map[[a,b]] = c
I tried it at the Firebug console and it seemed to work. Is that a good way to do it?
Another alternative I thought of is to create a string out of the tuples
my_map[""+a+":"+b] = c
So the question is: is there any problem with any of these methods? Is there a better way?
EDIT:
Small clarification: in my case, a,b,c are all integers

EcmaScript doesn't distinguish between indexing a property by name or by [], eg.
a.name
is literally equivalent to
a["name"]
The only difference is that numbers, etc are not valid syntax in a named property access
a.1
a.true
and so on are all invalid syntax.
Alas the reason all of these indexing mechanisms are the same is because in EcmaScript all property names are strings. eg.
a[1]
is effectively interpreted as
a[String(1)]
Which means in your example you do:
my_map[[a,b]] = c
Which becomes
my_map[String([a,b])] = c
Which is essentially the same as what your second example is doing (depending on implementation it may be faster however).
If you want true value-associative lookups you will need to implement it yourself on top of the js language, and you'll lose the nice [] style access :-(

You could use my jshashtable and then use any object as a key, though assuming your tuples are arrays of integers I think your best bet is one you've mentioned yourself: use the join() method of Array to create property names of a regular object. You could wrap this very simply:
function TupleDictionary() {
this.dict = {};
}
TupleDictionary.prototype = {
tupleToString: function(tuple) {
return tuple.join(",");
},
put: function(tuple, val) {
this.dict[ this.tupleToString(tuple) ] = val;
},
get: function(tuple) {
return this.dict[ this.tupleToString(tuple) ];
}
};
var dict = new TupleDictionary();
dict.put( [1,2], "banana" );
alert( dict.get( [1,2] ) );

All object keys in Javascript are strings. Using my_map[[a,b]] = c will produce a key in my_map which is the result of [a,b].toString(): a.toString() + ',' + b.toString(). This may actually be desirable (and is similar to your use of a + ':' + b), but you may run into conflicts if your keys contain the separator (either the comma if you use the array as the key, or the colon if you write the string as you have in your example).
Edit: An alternate approach would be to keep a separate array for key references. Eg:
var keys = [
[a,b],
[c,d]
];
var my_map = {
'keys[0]': /* Whatever [a,b] ought to be the key for */,
'keys[1]': /* Whatever [c,d] ought to be the key for */
};

the most simple and "natural" way to achieve something similar is by using multidimensional arrays, like this:
var my_map = [["blah","blah","bla"],
["foo", "bla", 8],
[324, 2345, 235],
[true, false, "whatever..."]];

Related

Use arrays as keys for a dict

Note:
Asking about alternative methods for performing a task is not a matter of opinion.
Asking about pros and cons of such methods is not a matter of opinion.
This is what I am asking.
Asking which method is preferable, based on such pros and cons, is a matter of opinion.
This is not what I am asking.
I mean to use a dict (or a similar object) in Google apps script, with arrays as keys.
These (I didn't know) are converted to strings when used as dict keys.
The way I wrote my code, for what I needed, was working.
After I added some functionality, the limitations surfaced.
For instance, I want keys to be something like [<color>,<number>].
Then I mean to do
for (key in mydict) {
var c = key[0];
var n = key[1];
... work separately with c as a string and n as a number
}
I guess I could split key at the comma, and convert n to int, but if there is a less cumbersome alternative, I would go for that.
Plus, in a more general case the conversion of key back to its original type may be more involved.
One solution I found is with WeakMap.
I am currently trying it.
Regardless the fact it may work, is there any alternative?
What are possible pros and cons of those other options?
It would be very good to know before rewriting all code.
Use nested objects.
mydict = {
"color1": {
1: <somevalue>,
5: <othervalue>
},
"color2": {
3: <value3>,
10: <value4>
}
};
Then use nested loops:
Object.entries(mydict).forEach(([c, item]) =>
Object.entries(item).forEach([n, value]) => {
// do something with c, n, value
})
);
One simple option is
for (keystr in mydict) {
var key = keystr.split(',');
var c = key[0];
var n = Number(key[1]);
... work separately with c as a string and n as a number
}
which works as long as none of the elements in the keys contain ','.

How to pass an array as argument to a function in javascript?

I'm trying to make helper functions to make use of the Google Analytics API, and I have a simple problem building strings. The scenario is, I have to apply a filter, and there may be n number of filters (a nominal amount, not more than 128 anyhow). I wanted to write a function which can take in the n strings and combine them with comma-separation in between.
I don't know if the number of arguments can be variable in javascript, and if it can take arrays as arguments in javascript (I am a newbie to JS), but I see no difference as variables are simply var and there is no datatype anywhere in JS (I come from a C++/Java background and find it confusing as it is). So I tried passing an array as an argument to a function so that the no. of things I can work with can be dynamic, decided by the elements in the array.
When I started searching for solutions, I came across this page. After that I recently came across this thread which also refers the same link and the format they have provided does me no good.
For the sake of clarity, let me provide the function definition I've written.
/**
* Utility method to build a comma-ed string from an array of strings
* for the multiple-condition requests to the GA API
*/
function buildString(strArray)
{
var returnString='';
for(var x in strArray)
returnString+=x+',';
return returnString = returnString.substring(0, returnString.length - 1);
}
And this is how I call it:
buildString.apply(this,[desc(visits),source])
where desc(visits) and source are both strings, so I assumed I'm sending an array of strings. Strangely, both this and null in the apply() call to the buildString function give me "0,1" as the return value.
Please tell me where I'm going wrong. Am I passing the array in a wrong manner? Or is my function definition wrong? Or is there some other simpler way to achieve what I'm trying?
Passing arrays to functions is no different from passing any other type:
var string = buildString([desc(visits), source]);
However, your function is not necessary, since Javascript has a built-in function for concatenating array elements with a delimiter:
var string = someArray.join(',');
You're over complicating things — JavaScript arrays have a built-in join method:
[ desc( visits ), source ].join( ',' );
EDIT: simpler still: the toString method:
[ desc( visits ), source ].toString();
The easiest would be to use the built-in join method:
[desc(visits), source].join(',');
Anyway, your problem was in the for..in loop
Instead of this:
for(var x in strArray){
returnString+=x+',';
}
You should have:
for(var i in strArray){
var x = strArray[i]; //Note this
returnString+=x+',';
}
Because for...in gives back the index/key, not the actual element as foreach does in other languages
Also your call should be:
buildString.call(this,[desc(visits),source]) or just buildString([desc(visits),source])
Cheers
Js argument can be any type and no limit to the number of argument,
But it is recommanded use 3-4 arguments at most, if there are more args, you can pass it as an object or array.
You don't need to worry about the type of args, js will do the job.
For example:
var func1 = function(a) {
console.log(a);
}
func1('good');
func1(1);
func1(['good', 'a', 1]);
func1({name: 'fn1', age: 12});
Anything you like!,
You can even define a function with three arguments, but only pass one is ok!
var func2 = function(a, b, c) {
console.log(a);
}
func2(1);
func2(1, 'good');
func2(1, 'good', 'night', 4);
And default array obj has many build-in func; for example:
var arr = ['good', 'night', 'foo', 'bar']; //define any thing in a array
str = arr.join(','); //you may get 'good,night,foo,bar'
var arr1 = str.split(','); // you may get ['good', 'night', 'foo', 'bar'];

JavaScript: Functional mapping?

Is there a more succinct way to write this?
var me = {};
for (var i in you) {
me[i] = you[i];
}
(where you is an arbitrarily-lengthed JavaScript array)
In other words, given the input of:
var you = [
"what",
"r",
"u"
];
The output, me, becomes:
me = {
0: "what",
1: "r",
2: "u"
};
Like, a one-liner that uses some functional method?
Why do you want to do this? Arrays in JavaScript are Objects, except with some additional properties like slice, splice, push and length.
Internally, Arrays and Objects are stored exactly in the same way: e.g. array[0] is the same as array["0"], or object["0"] (unlike in other languages where adjacent array indices are in fact in adjacent memory - array "indices" are simply converted into strings in JavaScript).
So, if you just want to copy the data, then this will suffice:
me = you.slice(); // me is a copy of you, but is still an array
Or, if you really want some sort of mapping functionality, then underscore.js provides a whole collection of functional programming tools for your perusal.
There is no built-in function that does what you ask, however some of the widely used javascript libraries like jQuery provide such a function. In jQuery's case: jQuery.extend()
Usage:
var me = {};
jQuery.extend(me,someObject);
//or, equivalently -
var me2 = jQuery.extend({},someObject);
jQuery has an extend() function (documentation here). Your code would look like this:
var me = {};
var you = ["what", "r", "u"];
$.extend(me, you);
This would allow you to do things like:
alert("Second element: " + me[1]);
It's a little odd, but I think it's what you're looking for.
I saw what you were trying to achieve with your string formatter. Instead of answering your original question of coming up with a concise implementation of one portion of it, I'll suggest a concise (and more flexible) implementation for the whole thing:
String.prototype.format = function () {
var args = arguments;
return this.replace(/\{(?:(\d+)|(\w+))\}/g, function (s, idx, prop) {
return prop && args[0]
? args[0][prop]
: args[idx];
});
};
When you have a number n inside a token "{n}", it uses the n-th argument for replacement. Otherwise, for non-numerical keys, it picks the corresponding property of the first argument.
For example:
"I have {1} {name}s in my basket.".replace({ type: "fruit", name: "eggplant" }, 4);
Returns:
"I have 4 eggplants in my basket."
The underscore.js library also has a basic extend function.
var me = _({}).extend(you)
or
var me = {}
_(me).extend(you)
or
var me = {}
_.extend(me, you)

Best way to convert string to array of object in javascript?

I want to convert below string to an array in javascript.
{a:12, b:c, foo:bar}
How do I convert this string into array of objects? Any cool idea?
I think that the best way of doing this, as Douglas Crockford (one of the biggests gurus of JavaScript) suggests in here is using the JSON native parser, as it is not only faster than the eval(), it's also more secure.
Native JSON parser is already available in:
Firefox 3.5+
IE 8+
Opera 10.5+
Safari Safari 4.0.3+
Chrome (don't know which version)
And Crockford has made a safe fallback in javascript, called json2.js, which is an adaption of the eval() approach, with some security bits added and with the native JSON parsers API. You just need to include that file, remove its first line, and use the native JSON parser, and if it's not present json2 would do the work.
Here is an example:
var myJSONString = '{ "a": 1, "b": 2 }',
myObject = JSON.parse(myJSONString);
Once parsed you'll get an object with attributes a and b, and as you may know, you can treat an object as a hash table or associative array in JavaScript, so you would be able to access the values like this:
myObject['a'];
If you just want a simple array and not an associative one you could do something like:
var myArray = [];
for(var i in myObject) {
myArray.push(myObject[i]);
}
Lastly, although not necessary in plain JavaScript, the JSON spec requires double quoting the key of the members. So the navite parser won't work without it. If I were you I would add it, but if it is not possible use the var myObject = eval( "(" + myString + ")" ); approach.
Since your string is malformed JSON, a JSON parser can't parse it properly and even eval() will throw an error. It's also not an Array but a HashMap or simply an Object literal (malformed). If the Object literal will only contain number and string values (and no child objects/arrays) you can use the following code.
function malformedJSON2Array (tar) {
var arr = [];
tar = tar.replace(/^\{|\}$/g,'').split(',');
for(var i=0,cur,pair;cur=tar[i];i++){
arr[i] = {};
pair = cur.split(':');
arr[i][pair[0]] = /^\d*$/.test(pair[1]) ? +pair[1] : pair[1];
}
return arr;
}
malformedJSON2Array("{a:12, b:c, foo:bar}");
// result -> [{a:12},{b:'c'},{foo:'bar'}]
That code will turn your string into an Array of Objects (plural).
If however you actually wanted a HashMap (Associative Array) and NOT an array, use the following code:
function malformedJSON2Object(tar) {
var obj = {};
tar = tar.replace(/^\{|\}$/g,'').split(',');
for(var i=0,cur,pair;cur=tar[i];i++){
pair = cur.split(':');
obj[pair[0]] = /^\d*$/.test(pair[1]) ? +pair[1] : pair[1];
}
return obj;
}
malformedJSON2Object("{a:12, b:c, foo:bar}");
// result -> {a:12,b:'c',foo:'bar'}
The above code will become a lot more complex when you start nesting objects and arrays. Basically you'd have to rewrite JSON.js and JSON2.js to support malformed JSON.
Also consider the following option, which is still bad I admit, but marginally better then sticking JSON inside an HTML tag's attribute.
<div id="DATA001">bla</div>
<!-- namespacing your data is even better! -->
<script>var DATA001 = {a:12,b:"c",foo:"bar"};</script>
I am assuming you omit quote marks in the string because you had put it inside an HTML tag's attribute and didn't want to escape quotes.
The simplest, but unsafe way to do it is:
eval('(' + myJSONtext + ')')
But since this will interpret any javascript code, it has security holes. To protect against this use a json parser. If you're using a framework (jquery, mootools, etc.) there's a framework-specific call. Most of them are based on Douglas Crawford's parser available at http://www.json.org/js.html.
You can use "for in"
var myObject = {a:'12', b:'c', foo:'bar'};
var myArray = [];
for(key in myObject) {
var value = myObject[key];
myArray[key] = value;
}
myArray['a']; // returns 12
Notes: considering that myObject only have one level of key-value pairs.
JSON.parse will do the trick. Once parsed, you can push them into the array.
var object = JSON.parse(param);
var array = [];
for(var i in object) {
array.push(object[i]);
}
If you're using jQuery, there's the $.parseJSON() function. It throws an exception if the string is malformed, and "Additionally if you pass in nothing, an empty string, null, or undefined, 'null' will be returned from parseJSON. Where the browser provides a native implementation of JSON.parse, jQuery uses it to parse the string"
Use safe evaluation. Unlike JSON.parse, this doesn't require the keys or values to be quoted. Quote values only if they contain embedded commas.
const myStr = "{a:1, b:2, c:3}";
const myObj = string_exp(myStr);
console.log("dot: " + myObj.c);
function string_exp(sCmd) {
return Function(`'use strict'; return (${sCmd})`)();
}
https://dev.to/spukas/everything-wrong-with-javascript-eval-35on#:~:text=the%20variable%20exists.-,Alternatives,-The%20most%20simple

JavaScript - Getting a name of an element in associative array

I have a JavaScript object that is treated as an associative array. Let's call it "fields". It has several elements, e.g.:
fields['element1'] = ...
fields['element2'] = ...
fields['element3'] = ...
Given fields[0], is it possible to obtain the name of the property (which is "element1") instead of its value?
Let's say you have an object oObject. It could be:
var oObject = {} ;
oObject["aaa"] = "AAA" ;
oObject["bbb"] = "BBB" ;
oObject["ccc"] = "CCC" ;
oObject["ddd"] = "DDD" ;
oObject["eee"] = "EEE" ;
Now, let's say you want to know its properties' names and values, to put into the variable strName and strValue. For that you use the "for(x in o)" construct, as in the following example:
var strName, strValue ;
for(strName in oObject)
{
strValue = oObject[strName] ;
alert("name : " + strName + " : value : " + strValue) ;
}
The "for(x in o)" construct will iterate over all properties of an object "o", and at each iteration, will put in variable "x" the current property name. All you have to do, then, to have its value, is to write o[x], but you already knew that.
Additional info
After some thinking, and after seeing the comment of Hank Gay, I feel additional info could be interesting.
Let's be naive (and forget the "in JavaScript, all objects, including arrays, are associative containers" thing).
You will usually need two kind of containers: Maps and Arrays.
Maps are created as in my example above (using the "o = new Object() ;" or the "o = {} ;" notation, and must be accessed through their properties. Of course, maps being maps, no ordering is guaranteed.
Arrays are created differently, and even if they can be accessed as maps, they should be accessed only through their indices, to be sure order is maintained.
Point is:
If you need a map, use a "new Object()" container
If you need an array, une an array, use a "new Array()" container
Don't EVER mix the two, and don't EVER access the map through indices, and for arrays, ALWAYS access its data through its indices, because if you don't follow those principles, you won't get what you want.
No, for two reasons.
fields[0] and fields["element1"] are different properties.
properties in an object are explicitly unordered
You could loop over the properties:
function (obj) {
for (prop in obj) {
if (obj.hasOwnProperty(prop) {
return prop;
}
}
};
…to get the "first" property for some arbitrary value of "first" that could change at any time.
http://ajaxian.com/archives/fun-with-browsers-for-in-loop explains the hasOwnProperty pattern.
There is no fields[0] (unless fields is an Array object, which supports numerical indices), so you can't get its name just like that. But you can simulate it like this:
function getKey(obj, i) {
var j = 0;
for (var p in obj) {
if (j++ == i) return p;
}
return null;
}
for (var p in obj) will loop through every field name in the object obj. By getting the nth field name, you can effectively get the "key" for a certain index.
Note that while it's working its way to become a standard, the order of field names is currently not guaranteed according to the standards, which means that after modifying the object, the same function call could theoretically return a different field name. Same thing goes that different browsers can return different results. Practically, you'll find that just about all the browsers do keep the order of field names so you shouldn't have to worry about it at all.
Just to point out what is implicit in everyone else's answer: "associative arrays" in Javascript are actually just Object instances, e.g.,
var aa = {};
aa.foo = 'argle';
alert(aa['foo']); // Will alert 'argle'
PLEASE don't use an Array instead of an Object—it has the potential to wreak havoc on for key in aa-style iteration.

Categories

Resources