2

I often find myself passing around objects that have IDs or names that identify them but are also a property of the object. For example:

type ExampleItemName = string;
interface ExampleItem {
  name: ExampleItemName ;
  value: number;
}

I also often need to create lists of these items, and I find it's convenient to be able to access the items by their names. The most logical data structure I see for this is a Map, ie:

type ExampleItemList = Map<ExampleItemName, ExampleItem>;

This is handy and convenient, as I can access any item by its name with Map.get but I can also easily iterate through the whole list using for...of.

However, this feels wrong to me because the name information is duplicated - it is present both in the key and the value of the map. If the name of an ExampleItem changes, the corresponding key in the map doesn't, so it feels fragile.

Is this a bad practice? If so, what's a better way to achieve the functionality I am looking for?

Ian
  • 5,704
  • 6
  • 40
  • 72

2 Answers2

1

This isn't bad practice per se, but there are a few conditions that need to be met or you run into problems like the problem you mentioned with the value you key it by changing. This pattern is usually referred to as the act of keying your data. It is best practice to key your data by values that you know will not change, like an ID. Since dictionaries can often reduce the cost of expensive find functions they are necessary in many situations, but like I mentioned and you pointed out, you must either key them by a value that will not change and is unique or you will need to update and handle duplicates every time you use the dictionary. Which solution is needed depends on your situation.

In addition, libraries like lodash have built in functions (which you can easily write yourself) that take in data and key them for you. You will also find these kinds ideas used extensively in other languages even if they go by different names (python: dictionary, Java: hashMap, etc.)

Isaac Welch
  • 163
  • 2
  • 9
  • Thank you for the insightful reply. I am using lodash but I am very new to it. Can you point me toward such a function? – Ian Jul 02 '19 at 13:42
  • @Ian the `keyBy` function is the name of the function lodash provides to key an object. It takes any object iterable by lodash (arrays, objects, etc.) and returns a dictionary where the keys are the property you specify in the second parameter. Ex: `_.keyBy(collectionOfItems, keyOfPropertyToUseAsId)` – Isaac Welch Jul 02 '19 at 14:21
0

If it's not terribly inconvenient, you can try to create your own list structure that satisfy your use case, such as the following one.

class NameMap<K, V extends { name: K }> {
    private items: Set<V> = new Set();
    Add(value: V) { this.items.add(value); }
    Get(key: K) {
        for(let e of this.items.values()) if(e.name == key) return e;
        return undefined;
    }
    [Symbol.iterator]() { return this.items[Symbol.iterator](); };
}

type ExampleItemList = NameMap<ExampleItemName, ExampleItem>;
Mu-Tsun Tsai
  • 2,348
  • 1
  • 12
  • 25
  • I could do this, but this implementation is no faster than just using an array of items and `Array.find` or similar to locate a particular one. Because `Get` requires iterating through all the elements, its _O(n)_ time complexity is much slower than the [_O(1)_](https://stackoverflow.com/a/33614512/1243041) of pulling an element from a hash map. – Ian Jul 02 '19 at 13:48
  • @Ian If you have to use `Map`, then I suppose you can create a wrapper class of `Map`, still using `ExampleItem.name` as keys, but make sure that the name can only be changed by a backdoor method provided by the wrapper class. Things could get more complicated if those items can be part of multiple maps, though. – Mu-Tsun Tsai Jul 02 '19 at 14:17