How to declare Hash.new(0) with 0 default value for counting objects in JavaScript?
ECMAScript does not have default values for missing keys in objects the same way Ruby does for Hash
es. You can, however, use dynamic introspective metaprogramming to do something similar, using ECMAScript Proxy
objects:
const defaultValue = 42;
const proxyHandler = {
get: (target, name) => name in target ? target[name] : defaultValue
};
const underlyingObject = {};
const hash = new Proxy(underlyingObject, proxyHandler);
1 in hash
//=> false
1 in underlyingObject
//=> false
hash[1]
//=> 42
underlyingObject[1]
//=> undefined
So, you could do something like this:
arr.reduce(
(acc, el) => { acc[el]++; return acc },
new Proxy(
{},
{ get: (target, name) => name in target ? target[name] : 0 }
)
)
//=> Proxy [ { '0': 5, '1': 3 }, { get: [Function: get] } ]
However, this is still not equivalent to the Ruby version, where the keys of the Hash
can be arbitrary objects whereas the property keys in an ECMAScript object can only be String
s and Symbol
s.
The direct equivalent of a Ruby Hash
is an ECMAScript Map
.
Unfortunately, ECMAScript Map
s don't have default values either. We could use the same trick we used for objects and create a Proxy
, but that would be awkward since we would have to intercept accesses to the get
method of the Map
, then extract the arguments, call has
, and so on.
Luckily, Map
s are designed to be subclassable:
class DefaultMap extends Map {
constructor(iterable=undefined, defaultValue=undefined) {
super(iterable);
Object.defineProperty(this, "defaultValue", { value: defaultValue });
}
get(key) {
return this.has(key) ? super.get(key) : this.defaultValue;
}
}
const hash = new DefaultMap(undefined, 42);
hash.has(1)
//=> false
hash.get(1)
//=> 42
This allows us to do something like this:
arr.reduce(
(acc, el) => acc.set(el, acc.get(el) + 1),
new DefaultMap(undefined, 0)
)
//=> DefaultMap [Map] { 1 => 3, 0 => 5 }
Of course, once we start defining our own Map
anyway, we might just go the whole way:
class Histogram extends DefaultMap {
constructor(iterator=undefined) {
super(undefined, 0);
if (iterator) {
for (const el of iterator) {
this.set(el);
}
}
}
set(key) {
super.set(key, this.get(key) + 1)
}
}
new Histogram(arr)
//=> Histogram [Map] { 1 => 3, 0 => 5 }
This also demonstrates a very important lesson: the choice of data structure can vastly influence the complexity of the algorithm. With the correct choice of data structure (a Histogram
), the algorithm completely vanishes, all we do is instantiate the data structure.
Note that the same is true in Ruby also. By choosing the right data structure (there are several implementations of a MultiSet
floating around the web), your entire algorithm vanishes and all that is left is:
require 'multiset'
Multiset[*arr]
#=> #<Multiset:#5 0, #3 1>
Creating a Hash with values as arrays and default value as empty array
Lakshmi is right. When you created the Hash using Hash.new([])
, you created one array object.
Hence, the same array is returned for every missing key in the Hash.
That is why, if the shared array is edited, the change is reflected across all the missing keys.
Using:
Hash.new { |h, k| h[k] = [] }
Creates and assigns a new array for each missing key in the Hash, so that it is a unique object.
Set default value of Javascript object attributes
Since I asked the question several years ago things have progressed nicely.
Proxies are part of ES6. The following example works in Chrome, Firefox, Safari and Edge:
let handler = {
get: function(target, name) {
return target.hasOwnProperty(name) ? target[name] : 42;
}
};
let emptyObj = {};
let p = new Proxy(emptyObj, handler);
p.answerToTheUltimateQuestionOfLife; //=> 42
Read more in Mozilla's documentation on Proxies.
Ruby hash default value behavior
The other answers seem to indicate that the difference in behavior is due to Integer
s being immutable and Array
s being mutable. But that is misleading. The difference is not that the creator of Ruby decided to make one immutable and the other mutable. The difference is that you, the programmer decided to mutate one but not the other.
The question is not whether Array
s are mutable, the question is whether you mutate it.
You can get both the behaviors you see above, just by using Array
s. Observe:
One default Array
with mutation
hsh = Hash.new([])
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => ['one', 'two']
# Because we mutated the default value, nonexistent keys return the changed value
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
One default Array
without mutation
hsh = Hash.new([])
hsh[:one] += ['one']
hsh[:two] += ['two']
# This is syntactic sugar for hsh[:two] = hsh[:two] + ['two']
hsh[:nonexistant]
# => []
# We didn't mutate the default value, it is still an empty array
hsh
# => { :one => ['one'], :two => ['two'] }
# This time, we *did* mutate the hash.
A new, different Array
every time with mutation
hsh = Hash.new { [] }
# This time, instead of a default *value*, we use a default *block*
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => {}
# But we never mutated the hash itself, therefore it is still empty!
hsh = Hash.new {|hsh, key| hsh[key] = [] }
# This time, instead of a default *value*, we use a default *block*
# And the block not only *returns* the default value, it also *assigns* it
hsh[:one] << 'one'
hsh[:two] << 'two'
hsh[:nonexistent]
# => []
# We *did* mutate the default value, but it was a fresh one every time.
hsh
# => { :one => ['one'], :two => ['two'], :nonexistent => [] }
rails nested hash with default values
The doc for Hash::new explains the three ways of initializing a hash and, in your case, you are using an object in the Hash constructor:
If obj is specified, this single object will be used for all default values.
If you want that each missing key creates it's own object, create the hash with a block, like this:
h = Hash.new { |h,k| h[k] = { count: 0, rating: 0 } }
Then:
2.6.3 :012 > h
=> {}
2.6.3 :013 > h['a'][:count] = 5
=> 5
2.6.3 :015 > h
=> {"a"=>{:count=>5, :rating=>0}}
In Ruby, how to set a default value for a nested hash?
Apparently I only had to do:
hash = Hash.new { |h,k| h[k] = Hash.new(0) }
Whoops. I'll try not to be so hasty to ask a question next time.
How to get value from Object, with default value
Looks like finally lodash has the _.get() function for this!
Related Topics
Good Reasons Why Not to Use Iframes in Page Content
What Events Does an <Input Type="Number" /> Fire When Its Value Is Changed
How Does Github Change the Url But Not the Reload
How to Block Website from Loading in Iframe
How to Call a Js Function Using Onclick Event
"Status Code:200 Ok (From Serviceworker)" in Chrome Network Devtools
Html5 Drag & Drop Change Icon/Cursor While Dragging
How to Set HTML Content into an Iframe
Animated Gif Only Loops Once in Chrome and Firefox
Adding Images to an HTML Document with JavaScript
Jquery Jump or Scroll to Certain Position, Div or Target on the Page from Button Onclick
How to Get a Number Value from an Input Field