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
18 Answers
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) // [] 5Check 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]] 0I 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); 1A 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")); // 43The 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}