In python you can have a defaultdict(int) which stores int as values. And if you try to do a 'get' on a key which is not present in the dictionary you get zero as default value.
Can you do the same in javascript/jquery
In python you can have a defaultdict(int) which stores int as values. And if you try to do a 'get' on a key which is not present in the dictionary you get zero as default value.
Can you do the same in javascript/jquery
You can build one using a JavaScript Proxy
var defaultDict = new Proxy({}, {
get: (target, name) => name in target ? target[name] : 0
})
This lets you use the same syntax as normal objects when accessing properties.
defaultDict.a = 1
console.log(defaultDict.a) // 1
console.log(defaultDict.b) // 0
To clean it up a bit, you can wrap this in a constructor function, or perhaps use the class syntax.
class DefaultDict {
constructor(defaultVal) {
return new Proxy({}, {
get: (target, name) => name in target ? target[name] : defaultVal
})
}
}
const counts = new DefaultDict(0)
console.log(counts.c) // 0
EDIT: The above implementation only works well with primitives. It should handle objects too by taking a constructor function for the default value. Here is an implementation that should work with primitives and constructor functions alike.
class DefaultDict {
constructor(defaultInit) {
return new Proxy({}, {
get: (target, name) => name in target ?
target[name] :
(target[name] = typeof defaultInit === 'function' ?
new defaultInit().valueOf() :
defaultInit)
})
}
}
const counts = new DefaultDict(Number)
counts.c++
console.log(counts.c) // 1
const lists = new DefaultDict(Array)
lists.men.push('bob')
lists.women.push('alice')
console.log(lists.men) // ['bob']
console.log(lists.women) // ['alice']
console.log(lists.nonbinary) // []
Check out pycollections.js:
var collections = require('pycollections');
var dd = new collections.DefaultDict(function(){return 0});
console.log(dd.get('missing')); // 0
dd.setOneNewValue(987, function(currentValue) {
return currentValue + 1;
});
console.log(dd.items()); // [[987, 1], ['missing', 0]]
I don't think there is the equivalent but you can always write your own. The equivalent of a dictionary in javascript would be an object so you can write it like so
function defaultDict() {
this.get = function (key) {
if (this.hasOwnProperty(key)) {
return key;
} else {
return 0;
}
}
}
Then call it like so
var myDict = new defaultDict();
myDict[1] = 2;
myDict.get(1);
A quick dirty hack can be constructed using Proxy
function dict(factory, origin) {
return new Proxy({ ...origin }, {
get(dict, key) {
// Ensure that "missed" keys are set into
// The dictionary with default values
if (!dict.hasOwnProperty(key)) {
dict[key] = factory()
}
return dict[key]
}
})
}
So the following code:
n = dict(Number, [[0, 1], [1, 2], [2, 4]])
// Zero is the default value mapped into 3
assert(n[3] == 0)
// The key must be present after calling factory
assert(Object.keys(n).length == 4)
Proxies definitely make the syntax most Python-like, and there's a library called defaultdict2 that offers what seems like a pretty crisp and thorough proxy-based implementation that supports nested/recursive defaultdicts, something I really value and am missing in the other answers so far in this thread.
That said, I tend to prefer keeping JS a bit more "vanilla"/"native" using a function-based approach like this proof-of-concept:
class DefaultMap {
constructor(defaultFn) {
this.defaultFn = defaultFn;
this.root = new Map();
}
put(...keys) {
let map = this.root;
for (const key of keys.slice(0, -1)) {
map.has(key) || map.set(key, new Map());
map = map.get(key);
}
const key = keys[keys.length-1];
map.has(key) || map.set(key, this.defaultFn());
return {
set: setterFn => map.set(key, setterFn(map.get(key))),
mutate: mutationFn => mutationFn(map.get(key)),
};
}
get(...keys) {
let map = this.root;
for (const key of keys) {
map = map?.get(key);
}
return map;
}
}
// Try it:
const dm = new DefaultMap(() => []);
dm.put("foo").mutate(v => v.push(1, 2, 3));
dm.put("foo").mutate(v => v.push(4, 5));
console.log(dm.get("foo")); // [1, 2, 3, 4, 5]
dm.put("bar", "baz").mutate(v => v.push("a", "b"));
console.log(dm.get("bar", "baz")); // ["a", "b"]
dm.put("bar", "baz").set(v => 42);
console.log(dm.get("bar", "baz")); // 42
dm.put("bar", "baz").set(v => v + 1);
console.log(dm.get("bar", "baz")); // 43
The constructor of DefaultMap accepts a function that returns a default value for leaf nodes. The basic operations for the structure are put and get, the latter of which is self-explanatory. put generates a chain of nested keys and returns a pair of functions that let you mutate or set the leaf node at the end of these keys. Accessing .root gives you the underlying Map structure.
Feel free to leave a comment if I've overlooked any bugs or miss useful features and I'll toss it in.
To add to Andy Carlson's answer
If you default dict an array, you'll get a toJSON field in the resulting object. You can get rid of it by deconstructing to a new object.
const dd = new DefaultDict(Array);
//...populate the dict
return {...dd};
The original answer does not seem to work on the nested cases. I made some modifications to make it work:
class DefaultDict {
constructor(defaultInit) {
this.original = defaultInit;
return new Proxy({}, {
get: function (target, name) {
if (name in target) {
return target[name];
} else {
if (typeof defaultInit === "function") {
target[name] = new defaultInit().valueOf();
} else if (typeof defaultInit === "object") {
if (typeof defaultInit.original !== "undefined") {
target[name] = new DefaultDict(defaultInit.original);
} else {
target[name] = JSON.parse(JSON.stringify(defaultInit));
}
} else {
target[name] = defaultInit;
}
return target[name];
}
}
});
}
}
var a = new DefaultDict(Array);
a["banana"].push("ya");
var b = new DefaultDict(new DefaultDict(Array));
b["orange"]["apple"].push("yo");
var c = new DefaultDict(Number);
c["banana"] = 1;
var d = new DefaultDict([2]);
d["banana"].push(1);
var e = new DefaultDict(new DefaultDict(2));
e["orange"]["apple"] = 3;
var f = new DefaultDict(1);
f["banana"] = 2;
The difference is that if defaultInit is an object, we need to return a deep copy of the object, instead of the original one.
Inspired by @Andy Carlson's answer, here's an implementation that works in a slightly more Pythonic way:
class DefaultDict {
constructor(defaultVal) {
return new Proxy(
{},
{
get: (target, name) => {
if (name == '__dict__') {
return target;
} else if (name in target) {
return target[name];
} else {
target[name] = defaultVal;
return defaultVal;
}
},
}
);
}
}
Basically, it also lets you retrieve all the gotten and set values of the "target", similar to how collections.defaultdict works in Python. This allows us to do things like:
const myDict = new DefaultDict(0);
myDict['a'] += 1;
myDict['b'] += 2;
myDict['c'] += 3;
myDict['whatever'];
console.log(myDict.__dict__);
// {'a': 1, 'b': 2, 'c': 3, 'whatever': 0}