【问题标题】:Defaultdict equivalent in javascriptjavascript中的Defaultdict等价物
【发布时间】:2013-10-08 06:57:03
【问题描述】:

在 python 中,您可以使用 defaultdict(int) 将 int 存储为值。如果您尝试对字典中不存在的键执行“获取”,则默认值为零。

你能在 javascript/jquery 中做同样的事情吗

【问题讨论】:

标签: javascript jquery


【解决方案1】:

我认为没有等价物,但您始终可以自己编写。 javascript中字典的等价物是一个对象,所以你可以这样写

function defaultDict() {
    this.get = function (key) {
        if (this.hasOwnProperty(key)) {
            return key;
        } else {
            return 0;
        }
    }
}

那就这样称呼吧

var myDict = new defaultDict();
myDict[1] = 2;
myDict.get(1);

【讨论】:

  • 如果您想将默认值设为集合(例如列表的映射),这将不起作用。
【解决方案2】:

查看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]]

【讨论】:

    【解决方案3】:

    您可以使用 JavaScript Proxy 构建一个

    var defaultDict = new Proxy({}, {
      get: (target, name) => name in target ? target[name] : 0
    })
    

    这让您在访问属性时可以使用与普通对象相同的语法。

    defaultDict.a = 1
    console.log(defaultDict.a) // 1
    console.log(defaultDict.b) // 0
    

    要稍微清理一下,您可以将其包装在构造函数中,或者使用类语法。

    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
    

    编辑: 上面的实现只适用于原语。它也应该通过将构造函数作为默认值来处理对象。这是一个应该与原语和构造函数一起使用的实现。

    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) // []
    

    【讨论】:

    • 我喜欢这个答案,但如果 defaultVal 是一个 array (或通过引用修改的任何内容?)可能会产生意想不到的结果。我使用了这个变体:dd = new Proxy({}, { get: (target, name) => name in target ? target[name] : (target[name]=[]) }),或者这个变体:class DefaultDict2 { constructor(defaultValConstructor) { return new Proxy({}, { get: (target, name) => name in target ? target[name] : new defaultValConstructor() }) } }
    • 我也喜欢这个答案,你能扩展它以显示嵌套的默认字典吗?
    • 最后一个版本是这样的,所以在countslists 对象中有一个toJSON 键。有没有办法解决这个问题?
    • 很好的答案!另外需要注意的是,即使你只从一个属性中读取,它也会创建默认元素,所以如果你执行console.log(dict.a) 然后使用Object.keys(dict)a 仍然会出现,即使你从未分配它一个值。
    • name in target ? target[name] : defaultVal 看起来像一个错误。我认为应该是name in target ? target[name] : target[name] = defaultVal
    【解决方案4】:

    添加到安迪卡尔森的答案

    如果您默认 dict 一个数组,您将在结果对象中获得一个 toJSON 字段。您可以通过解构为新对象来摆脱它。

    const dd = new DefaultDict(Array);
    //...populate the dict
    return {...dd};
    

    【讨论】:

      【解决方案5】:

      可以使用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]
              }
          })
      }
      

      所以下面的代码:

      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)
      

      【讨论】:

        【解决方案6】:

        Proxies 绝对让语法最像 Python,还有一个名为 defaultdict2 的库,它提供了一个看起来非常清晰和彻底的基于代理的实现,它支持 nested/recursive defaultdicts,这是我真正重视和缺少的东西在此线程中到目前为止的其他答案中。

        也就是说,我倾向于使用基于函数的方法(如概念验证)让 JS 更“原味”/“原生”:

        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

        DefaultMap 的构造函数接受一个返回叶节点默认值的函数。该结构的基本操作是putget,后者是不言自明的。 put 生成嵌套键链并返回一对函数,可让您在这些键的末尾变异或设置叶节点。访问 .root 可以得到底层的 Map 结构。

        如果我忽略了任何错误或错过了有用的功能,请随时发表评论,我会扔进去。

        【讨论】:

          【解决方案7】:

          原始答案似乎不适用于嵌套案例。我做了一些修改以使其工作:

            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;
          

          不同的是,如果 defaultInit 是一个对象,我们需要返回一个对象的深拷贝,而不是原始的。

          【讨论】:

            猜你喜欢
            • 2013-11-14
            • 1970-01-01
            • 2014-02-27
            • 2013-08-25
            • 1970-01-01
            • 2014-01-08
            • 1970-01-01
            • 1970-01-01
            • 2016-08-30
            相关资源
            最近更新 更多