Designing a MultiMap (in DOM, would like to be consistent with ES)

Tab Atkins Jr. jackalmage at gmail.com
Mon Dec 3 09:32:31 PST 2012


I've written up the proposal properly as executable code, a la the Map proposal:

  /** A non-stupid alternative to Array.prototype.indexOf */
  function indexOfIdentical(keys, key) {
    for (var i = 0; i < keys.length; i++) {
      if (keys[i] is key) { return i; }
    }
    return -1;
  }

  import {Name} from '@name';
  const keysName = new Name;  // These should be non global.
  const valsName = new Name;

  class MultiMap {
    constructor(iterable = []) {
      this[keysName] = [];
      this[valsName] = [];
      for (let [k, v] of iterable) {
        this.append(k, v);
      }
    }
    get(key) {
      const keys = this[keysName];
      const i = indexOfIdentical(keys, key);
      return i < 0 ? undefined : this[valsName][i];
    }
    getAll(key) {
      const keys = this[keysName];
      const vals = this[valsName];
      const arr = [];
      for(var i = 0; i < keys.length; i++) {
        if(keys[i] is key) { arr.push(vals[i]); }
      }
      return arr;
    }
    has(key, ...val) {
      const keys = this[keysName];
      if(val.length == 0) {
        return indexOfIdentical(keys, key) >= 0;
      } else {
        const vals = this[valsName];
        for(const [i,k] of keys) {
          if(k is key && vals[i] is val[0]) { return true; }
        }
        return false;
      }
    }
    set(key, ...val) {
      const keys = this[keysName];
      const vals = this[valsName];
      var vali = 0;
      for(var i = 0; i < keys.length; i++) {
        if(keys[i] is key) {
          if(vali < val.length) {
            vals[i] = val[vali];
            vali++;
          } else {
            keys.splice(i,1);
            vals.splice(i,1);
            i--;
          }
        }
      }
      if(vali < val.length) {
        this.push(key, ...val.slice(vali));
      }
      return this;
    }
    append(key, ...val) {
      const keys = this[keysName];
      const vals = thsi[valsName];
      val.forEach((e)=>{keys.push(key); vals.push(val);});
      return this;
    }
    delete(key, ...val) {
      const keys = this[keysName];
      const vals = this[valsName];
      for(let i = 0; i < keys.length; i++) {
        if(keys[i] is key && (val.length == 0 ||
indexOfIdentifical(val,vals[i]) >= 0))
          keys.splice(i,1);
          vals.splice(i,1);
          i--;
        }
      }
      return this;
    }
    *items() {
      for (var i = 0; i < this[keysName].length; i++) {
        yield [this[keysName][i], this[valsName][i]];
      }
    }
    *groupedItems() {
      var seenAlready = new Set();
      var keys = this[keysName];
      for(var i = 0; i < keys.length; i++) {
        if(seenAlready.has(keys[i])) continue;
        seenAlready.add(keys[i]);
        yield [keys[i], this.getAll(keys[i])];
      }
    }
    *keys() {
      for (var i = 0; i < this[keysName].length; i++) {
        yield this[keysName][i];
      }
    }
    *values() {
      for (var i = 0; i < this[keysName].length; i++) {
        yield this[valsName][i];
      }
    }
  }

  Object.defineProperty(MultiMap.prototype, "iterator", {configurable:
true, writable: true, value: MultiMap.prototype.items});



Changes from the initial proposal:

1. I've eliminated every *All method except getAll, and replaced them
by just making the normal methods variadic, as implicitly suggested by
Kris.  I expect the spread operator to solve the "but I've got an
array of values!" problem elegantly, but until then .apply will work.
2. I've added a groupedItems generator, which returns only unique keys
along with an array of all associated values.

I've kept append rather than changing to push as Kris suggests.
There's already not any agreement about what this kind of method
should be called in ES (it's Array#push, but Set#add), while the DOM
has generally standardized on "append" as the verb for this kind of
thing.  As well, the fact that it has a distinguished key argument
makes it kinda different from Array#push.

Any further thoughts?  If not, we'll go forward with this design in
the URL spec, and hopefully when TC39 fills in behind us with a
MultiMap class it can match up with what we've done.

~TJ


More information about the es-discuss mailing list