Generic 2D hash in JavaScript? - javascript

In other languages it is possible to create a generic 2D hash. I know creating 2d hashes is possible in javascript as well as explained here, but I can't seem to find a generic way to achieve this.
As an example of what I am looking for. In Ruby you can do this:
2dhash = Hash.new{|h, k| h[k] = Hash.new }
puts 2dhash["test"]["yes"]
#=> nil
2dhash[1][2] = "hello"
puts 2dhash[1][2]
#=> "hello"
Notice that I have not initialized the second level of hash, it happens automatically.
Is it possible to somehow achieve the same in javascript? Specifically, a way to make a 2d hash without initializing the first level of hash (or hard-coding it to be even more specific). The 2dhash will be used dynamically, so I have no clue what the first level will be.

Looks like a nice data structure excercise, let me try :D
function Hash() {
this.hash = {};
}
Hash.prototype.set = function(val) {
var paths = Array.prototype.slice.call(arguments, 1) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
if (!(hashed instanceof Hash)) {
hashed = this.hash[path] = new Hash()
}
Hash.prototype.set.apply(hashed, [val].concat(paths))
} else {
// last level
this.hash[path] = val
}
}
Hash.prototype.get = function() {
var paths = Array.prototype.slice.call(arguments, 0) // all levels
var path = paths.shift() // first level
var hashed = this.hash[path]
if (paths.length) {
// still have deeper levels
return Hash.prototype.get.apply(hashed, paths)
} else {
// last level
return hashed
}
}
Now, let's see if it works:
var trytry = new Hash()
trytry.set('the value to store', 'key1', 'key2')
trytry.get('key1') // Hash{key2: 'the value to store'}
trytry.get('key1', 'key2') // 'the value to store'
Hooray it works!
It also works for even deeper levels:
trytry.set('the value to store', 'key1', 'key2','key3', 'key4')
trytry.get('key1', 'key2','key3') // Hash{key4: 'the value to store'}
However, a disadvantage of this approach is that you have to use instance methods get and set, rather than native object literal getter/setter.
It's still incomplete. For production environment, we need to do more, e.g. methods and properties like contains, size, etc.

If you initialize the first level of the hash with objects, then you can reference the second level without typeErrors, even if the data was not defined before.
Example:
var _2dhash = {a: {}, b: {}, c:{}}
//Note you cannot start variable names with numbers in js
_2dhash['a']['missingElement'];
// > undefined
It works because you're accessing undefined properties of defined objects. If you try to access through a missing top-level object, ie.
_2dhash['d']['whatever'];
You will get a TypeError, because _2dhash.d was not defined, and the second lookup fails, trying to read the 'whatever' property of undefined.

Related

Alternate/better ways to initialize JavaScript object that needs multiple static values?

I have a JavaScript object with some static attribute values, dynamic attribute values and methods. Each time I need one of these objects, I will need 10 of them. Each of the 10 objects gets initialized by a dedicated object literal. That happens under 3 different contexts of a user doing something on a data entry form. User actions can cause the contexts to happen in any order, any number of times, but the same 10 objects will always be created in each context. By "same" I mean the static values for a "no_matl" object will be identical each time a "no_matl" object is created ... only a few dynamic attribute values (field value, previous value, date/time, context ID) are different for each context.
Is there a smarter way to do the initialization currently done with the const object literal? Originally I passed a bunch of params to the constructor and initialized the static attributes from those. The object literal approach seemed cleaner. Maybe there's a better way?
// object literals used to initialize a each of the 10
// different type objects.
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
const FIELD_CPCAT = { ... same attributes, different values ...};
const FIELD_HCN = { ... same attributes, different values ...};
// ... 7 more like this ...
// context 1
var no_matl = new MyField(FIELD_NOMATERIAL),
cpcap = new MyField(FIELD_CPCAT),
hcn = new MyField(FIELD_HCN) .... 7 more like this
// object definition
function MyField() {
if (arguments.length == 1 && typeof(arguments[0]) === 'object' ) {
this.DispName = arguments[0].DispName ;
this.DbName = arguments[0].DbName ;
// .... etc for rest of static attributes ...
}
}
Sounds like what you want is a copy of the original object that can change values without changing the original. Try this:
const FIELD_NOMATERIAL = {
DispName: 'No Material',
DbName: 'NO_MATERIAL',
TrueVal: 'Yes',
InitVal: '',
DispWhenSet: 'yes',
DispWhenNotSet: ''
};
function getFreshCopy(original) {
return Object.assign({}, original);
}
var no_matl = getFreshCopy(FIELD_NOMATERIAL);
Using Object.assign({}, obj) will create a new copy that can be changed without the original values changing. no_matl can be adjusted and FIELD_NOMATERIAL remains in its original state.
Note that const means the variable cannot be assigned a new value. It does not mean that the contents of the object cannot be changed. That means the following is true:
const noChange = { a: 7 };
noChange.a = 8; // this is fine because 'a' is allowed to change
noChange = "hello"; // this gives TypeError: Assignment to constant variable.

Accessing plugin prototype function using array square [] brackets

I am very new to JS and I was just going through the syntax of modal.js. Basically I have a small difficulty, a lot of classical JS plugins use the below skeleton code for the plugin:
var Modal = function(element , options){
this.options = options
this.$body = $(document.body)
this.$element = $(element)
this.isShown = null
this.$backdrop =
this.scrollbarWidth = 0
}
Modal.prototype.toggle = function (_relatedTarget) {
// do something
}
Modal.prototype.show = function (_relatedTarget) {
// do something
}
var data = new Modal(somthing , radnom);
// now if we assume that option is "show",
//the show function in Modal will be executed
// but my question is data is not an array, so how can we use
// [] square brackets to access the properties of Modal/data ??
data[option](_relatedtarget);
Now my question is about accessing the properties of a plugin, see how a function is being called using the following syntax:
data[option](_relatedtarget);
See my comment in the code. How can we access the properties of data using []; it's not an array, right?
[] are not just for arrays
You can use [] to access properties on an object too.
You can use
data["show"] to access the show method
OR
data.show which is the same thing
One advantage of the [] is that you can use a variable within the brackets
var option = "show";
data[option](something); // call the `show` method on `data`
If you know the method you want to call, using the . is much nicer looking in the code
data.show(something); // much quicker (to type), and prettier
JavaScript has arrays:
var anArray = [ 1, 2, 3, 4 ];
and associative arrays (also known as maps):
var anAssociativeArray = { first: "No. 1", second: 2, somethingElse: "Other" };
both of these data structures can be accessed via []:
anArray[3] // will get the element of the array in position 3
// (starting counting frrom 0).
anAssociativeArray['first'] // will get the element of the associative array with the
// key 'first'.
Associative arrays can also be accessed via the .key notation:
anAssociativeArray.first // will also get the property with key 'first'.
The . notation can be used if you know the key you want to access but if you want to dynamically select which key then you need to use the [] notation.
var whichOptionToPick = 'somethingElse';
var value = anAssociativeArray[ whichOptionToPick ]; // will get the value "Other".

Dynamically assigning properties to a JavaScript object (trie)

I'm trying to implement a variation of a trie in JavaScript. Basically, it's an efficient data storage object in which the characters in keys are not repeated. In other words, if I have the keys "abe" and "ann," only one instance of the shared letter "a" should appear:
{
a: {
b: {
e: {
0: 'lincoln'
}
},
n: {
n: {
0: 'mcgee'
}
}
}
}
Here is the desired implementation and a few usage examples:
function Trie () {
// The top level of the trie.
var root = {};
return {
write: function (key, value) {
},
read: function (key) {
}
};
}
// Sample usage
var trie = new Trie();
trie.write('abe', 'lincoln');
trie.write('ann', 'mcgee');
trie.read('abe'); // returns 'lincoln'
trie.read('ann'); // returns 'mcgee'
I've run into a blocker with respect to the write method. Given a string key such as "abe," I need to assign a property to root['a']['b']['e']. I can't find a way to assign a value to an object property several layers deep when the number of keys and the values of the keys are unknown.
The only solution that comes to mind is, I think, a bad one: placing the path to the value into a string and using eval. For example: eval("root['a']['b']['e'] = 'lincoln'");
Is there a better solution for dynamically assigning the values? (I realize that this is a bit of complicated problem, so I'm happy to clarify by providing extra information.)
a very naive approach (given the requirements,though i would write a different implementation)
given a string of keys and a pointer to the root,and a value to assign;
function write(root,path,value){
var a = path.split(''); // 'abc'->['a','b','c']
var pointer = root;
var i=0;
while(i<a.length-1){
if(pointer[a[i]] == undefined){
pointer[a[i]]={};
}
pointer = pointer[a[i]];
i++;
}
pointer[a[i]]=value;
return root;
}
EDIT : i'm assuming all the keys exist on their respective object. I added a if condition in case some keys are not defined.
EDIT:2 split corrected, correcting a little bug right now ;)
EDIT:3 should work now.
usage : write({},'abc',1) // yields {a:{b:{c:1}}}
what you're looking for is a double array trie.
you can do a github search for that, but the two main libraries listed are:
doublearray, from the documentation:
var doublearray = require('./doublearray.js');
var words = [
{ k: 'a', v: 1 },
{ k: 'abc', v: 2 },
];
var trie = doublearray.builder().build(words);
trie.contain('a'); // -> true
trie.lookup('abc'); // -> 2
or datrie

Javascript change variable based on variable reference

In a program i am writing i need an object variable that looks like this:
var map {
cube: {__pt3arraylocation__:[0,0,0], poly: new Object()},
other: {__pt3arraylocation__:[1,0,0], poly: new Object()}
};
However, i want to be able to type map.cube and have it return the pt3arraylocation as a default unless i specify what i want by typing map.cube.poly
for example: map.cube would return [0,0,0] and map.cube.poly would return the object poly in the object cube
thanks in advance
i want to be able to type map.cube and have it return the pt3arraylocation as a default unless i specify what i want by typing map.cube.poly
for example: map.cube would return [0,0,0] and map.cube.poly would return the object poly in the object cube
You can't do that in JavaScript.
However, as an alternative, it's worth noting that you can add arbitrary properties to arrays if you want to. So for instance:
var map {
cube: [0,0,0],
other: [1,0,0]
};
map.cube.poly = {}; // {} is the same as `new Object()` but not subject to people...
map.other.poly = {}; // ...overriding the `Object` symbol
Then map.cube gives you the array, and map.cube.poly gives you the object you've stored on that array.
How is this possible? Because in JavaScript, arrays aren't really arrays. They're just objects that have an automatic length property, treat a class of property names (all numeric ones) in a special way, and have Array.prototype backing them up. And of course, you can add properties to any object.
There's no literal syntax for doing this, which is why I had to do it with assignments after the object initializer above. But it's perfectly valid. I use it for cross-indexing arrays all the time.
Do be sure, if you do this, that you're not using for..in incorrectly; more.
The best way to do this I would say is like this:
var map {
cube: [0,0,0],
other: [1,0,0]
};
map.cube.poly = new Object();
map.other.poly = new Object();
That is not possible to achive. Maybe you can play around with toString()
var map = {
cube: {
__pt3arraylocation__:[0,0,0],
poly: new Object(),
toString : function() {
return this.__pt3arraylocation__.toString()
}
},
other: {__pt3arraylocation__:[1,0,0], poly: new Object()}
};
map.cube == '0,0,0'
map.cube.split(',') == map.cube.__pt3arraylocation__
There is no way to do that exactly as you want - if you request an object (which map.cube is), you get an object. However, there are a few ways to do something similar.
when used as a parameter to functions or operations that require string, like alert(map.cube) or "sometext" + map.cube, the object is converted to string by calling the toString function. You can therefore define, for example:
map.cube.toString = function() { return this.__pt3arraylocation__.toString(); };
a similar thing happens when there if the object is used in context when a number is needed. In this case, it is converted with valueOf(). So you can use, for example
map.cube.valueOf = function() { return this.__pt3arraylocation__.length; };
or you can obtain the default value via a function call, if you define your object as a function instead
var map = {
cube: function() {
return map.cube.__pt3arraylocation__;
}
}
map.cube.__pt3arraylocation__ = [0,0,0];
map.cube.poly = new Object();
alert(map.cube.__pt3arraylocation__); // 0,0,0
alert(map.cube.poly); // [object Object]
alert(map.cube()); // same as map.cube.__pt3arraylocation__
As you can see, in JavaScript, functions are objects like any other, so you can not only call them, but also set fields and methods to them.
T.J. Crowder is right. Based on his answer I'd like to remark that you can also assign map.cube like this
var map = {
cube: function(){
var val = [0,0,0];
val.poly = {};
return val; }()
};
Or for more values:
function polyval(val){
val.poly = {};
return val;
}
var map = {
cube: polyval([0,0,0]),
other: polyval([1,0,0]),
more: polyval([1,1,0])
/* ... etc */
};

JavaScript: Get first and only property name of object

If I want to enumerate the properties of an object and want to ignore prototypes, I would use:
var instance = { ... };
for (var prop in instance) {
if (instance.hasOwnProperty(prop)) {
...
}
}
What if instance only has one property, and I want to get that property name? Is there an easier way than doing this:
var instance = { id: "foobar" };
var singleMember = (function() {
for (var prop in instance) {
if (instance.hasOwnProperty(prop)) {
return prop;
}
}
})();
Maybe Object.keys can work for you. If its length returns 1, you can use yourObject[Object.keys[0]] to get the only property of the object. The MDN-link also shows a custom function for use in environments without the keys method1. Code like this:
var obj = {foo:'bar'},
kyz = Object.keys(obj);
if (kyz.length === 1){
alert(obj[kyz[0]]); //=> 'bar'
} else {
/* loop through obj */
}
1 Some older browsers don't support Object.keys. The MDN link supplies code to to make it work in these browsers too. See header Compatibility in the aforementioned MDN page
Shortest form:
instance[Object.keys(instance)[0]];
ES6+ function:
let first = v => v[Object.keys(v)[0]];
Use the function:
first({a:'first', b:'second'}) // return 'first'
var foo = {bar: 1};
console.log(Object.keys(foo).toString());
which will print the string
"bar"
Though my answer is downvoted, it's still worth to know that there is no such thing as order of keys in javascript object. Therefore, in theory, any code build on iterating values can be inconsistent. One approach could be creating an object and to define setter which actually provides counting, ordering and so on, and provide some methods to access this fields. This could be done in modern browsers.
So, to answer you question, in general you approach is still most closs-browser. You can iterate using lodash or any other modern framework wich will hide "hasOwnProperty" complexity from you. As of August'15 Object.keys can be accepted as cross-browser and universal. After all IE8 happened years ago. Still there are some cases when you just don't wont store all set of keys in array. But I'd go with Object.keys - it's more flexible compared to iteration.
Unfortunately, there is no, "list properties" function built in, and there certainly isn't a "getFirstProperty" (especially since there is no guarantee that any property will consistently be "first").
I think you're better off writing a function like this one:
/**
* A means to get all of the keys of a JSON-style object.
* #param obj The object to iterate
* #param count maximum length of returned list (defaults to Infinity).
*/
function getProperties( obj, count )
{
if( isNaN( count ) ) count = Infinity
var keys = []
for( var it in obj )
{
if( keys.length > count ) break;
keys.push( it );
}
return keys;
}
Then, you could access the name though:
instance = {"foo":"bar"}
// String() on an array of < 2 length returns the first value as a string
// or "" if there are no values.
var prop = String(getProperties(instance, 1));
This is an old post, but I ended up writing the following helper function based on Object.keys().
It returns the key and value of the first property.
getFirstPropertyKeyAndValue(sourceObject) {
var result = null;
var ownProperties = Object.keys(sourceObject);
if (ownProperties.length > 0) {
if (ownProperties.length > 1) {
console.warn('Getting first property of an object containing more than 1 own property may result in unexpected results. Ordering is not ensured.', sourceObject);
}
var firstPropertyName = ownProperties[0];
result = {key: firstPropertyName, value: sourceObject[firstPropertyName]};
}
return result;
}
Answers in here all good, and with the caveat that the order may be unreliable (although in practice it seems the order the properties are set tends to stay that way), this quick and dirty method also works:
var obj = {foo: 1, bar: 2};
for(var key in obj) {
//you could use key here if you like
break;
}
//key now contains your first key
or a shorter version should also do it:
for(var key in obj) break;
//key now contains your first key

Categories

Resources