如何声明默认值0的Hash.new(0)来计数JavaScript中的对象? [英] How to declare Hash.new(0) with 0 default value for counting objects in JavaScript?

查看:39
本文介绍了如何声明默认值0的Hash.new(0)来计数JavaScript中的对象?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试遍历一个数字数组,并计算在该数组中找到每个数字的次数.

在ruby中很简单,我只声明一个 Hash.new(0),并且该哈希已经设置为从0开始计数.例如:

  arr = [1,0,0,0,1,0,0,1]计数器= Hash.new(0)arr.each {| num |counter [num] + = 1}#给出{1 =>3,0 =>5} 

我想用JavaScript做同样的事情,但是 let counter = {} 给了我 {'0':NaN,'1':NaN} .

您知道如何在JavaScript中创建与对象相同的哈希吗?

解决方案

ECMAScript没有对象 哈希可以是任意对象,而 ECMAScript中的属性键对象只能是 String s和 符号 s.

与Ruby Hash 直接等效的是 ECMAScript 地图 .

不幸的是,ECMAScript Map 也不具有默认值.我们可以使用与对象相同的技巧并创建 Proxy ,但这很尴尬,因为我们必须拦截对 ,依此类推.

幸运的是, Map 被设计为可子类化的:

  class DefaultMap扩展了Map {构造函数(iterable = undefined,defaultValue = undefined){超级(可迭代);Object.defineProperty(this,"defaultValue",{value:defaultValue});}get(key){返回this.has(key)吗?super.get(key):this.defaultValue;}}const hash = new DefaultMap(undefined,42);hash.has(1)//=>错误的hash.get(1)//=>42 

这使我们可以执行以下操作:

  arr.reduce((acc,el)=>acc.set(el,acc.get(el)+1),新的DefaultMap(未定义,0))//=>DefaultMap [Map] {1 =>3,0 =>5} 

当然,无论如何,一旦开始定义自己的 Map ,我们可能会一路走来

  class Histogram扩展DefaultMap {构造函数(iterator = undefined){超级(未定义,0);如果(迭代器){对于(迭代器的常量el){this.set(el);}}}set(key){super.set(key,this.get(key)+1)}}新直方图(arr)//=>直方图[Map] {1 =>3,0 =>5} 

这也证明了一个非常重要的教训:数据结构的选择会极大地影响算法的复杂性.正确选择数据结构(<直方图)后,算法完全消失,我们要做的只是实例化数据结构.

请注意,在Ruby中也是如此.通过选择正确的数据结构(在网络上浮动 MultiSet 的几种实现方式),您可以使用整个算法消失,剩下的就是:

 需要'multiset'多集[* arr]#=>#<多重集:#5 0,#3 1> 

I'm trying to iterate through an array of digits and count how many times each digit is found in the array.

In ruby it's easy, I just declare a Hash.new(0) and that hash is already been set up for counting from 0 as a value. For example:

arr = [1,0,0,0,1,0,0,1]
counter = Hash.new(0)
arr.each { |num| counter[num] += 1 } # which gives {1=> 3, 0=> 5}

I wanted to do the same thing in JavaScript but let counter = {} gives me { '0': NaN, '1': NaN }.

Do you have any idea how to create that same Hash as an Object in JavaScript?

解决方案

ECMAScript does not have default values for missing keys in objects the same way Ruby does for Hashes. 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 Strings and Symbols.

The direct equivalent of a Ruby Hash is an ECMAScript Map.

Unfortunately, ECMAScript Maps 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, Maps 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>

这篇关于如何声明默认值0的Hash.new(0)来计数JavaScript中的对象?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆