static stableHasher(originalItems) {
    let items = [...originalItems];

    /** @type {Map<string, T>} */
    const assignedItems = new Map();
    return key => {
      // Key has already been assigned an item.
      const alreadyAssignedItem = assignedItems.get(key);
      if (alreadyAssignedItem !== undefined) return alreadyAssignedItem;

      // Ran out of items.
      if (items.length === 0) {
        items = [...originalItems];
      }

      // Select a random item using a stable hash.
      const hash = [...key].reduce((acc, char) => acc + char.charCodeAt(0), 0);
      const [assignedItem] = items.splice(hash % items.length, 1);
      assignedItems.set(key, assignedItem);

      return assignedItem;
    };
  }
Given a list of items, return a function (a hasher) that will map keys to an item.
When a key is seen for the first time, the item returned is cached and will always
be returned for the same key.
The hash function is stable and deterministic, so the same key->item mapping will be
produced given the same call order.
@template T
@param {T[]} originalItems
@return {(key: string) => T}