So, I came across this issue earlier today. Wasn't real thrilled with most of the solutions here, mostly because I wanted to be able to set normal keys, but also set a range of keys all at once when needed, so I came up with this:
class RangedDict(dict): """ A dictionary that supports setting items en masse by ranges, but also supports normal keys. The core difference between this and any other dict is that by passing a tuple of 2 to 3 numeric values, an inclusive range of keys will be set to that value. An example usage is: >>> d = RangedDict({ ... (1, 5): "foo" ... }) >>> print d[1] # prints "foo" >>> print d[4] # also prints "foo" >>> print d[5] # still prints "foo" because ranges are inclusive by default >>> d['bar'] = 'baz' >>> print d['bar'] # prints 'baz' because this also works like a normal dict Do note, ranges are inclusive by default, so 5 is also set. You can control inclusivity via the `exclusive` kwarg. The third, optional, parameter that can be given to a range tuple is a step parameter (analogous to the step parameter in xrange), like so: `(1, 5, 2)`, which would set keys 1, 3, and 5 only. For example: >>> d[(11, 15, 2)] = "bar" >>> print d[13] # prints "bar" >>> print d[14] # raises KeyError because of step parameter NOTE: ALL tuples are strictly interpreted as attempts to set a range tuple. This means that any tuple that does NOT conform to the range tuple definition above (e.g., `("foo",)`) will raise a ValueError. """ def __init__(self, data=None, exclusive=False): # we add data as a param so you can just wrap a dict literal in the class constructor and it works, instead of # having to use kwargs self._stop_offset = 0 if exclusive else 1 if data is None: data = {} for k, v in data.items(): if isinstance(k, tuple): self._store_tuple(k, v) else: self[k] = v def __setitem__(self, key, value): if isinstance(key, tuple): self._store_tuple(key, value) else: # let's go ahead and prevent that infinite recursion, mmmmmkay dict.__setitem__(self, key, value) def _store_tuple(self, _tuple, value): if len(_tuple) > 3 or len(_tuple) < 2: # eventually, it would be nice to allow people to store tuples as keys too. Make a new class like: RangeKey # to do this raise ValueError("Key: {} is invalid! Ranges are described like: (start, stop[, step])") step = _tuple[2] if len(_tuple) == 3 else 1 start = _tuple[0] stop = _tuple[1] # +1 for inclusivity for idx in xrange(start, stop + self._stop_offset, step): dict.__setitem__(self, idx, value)
It uses tuples to describe ranges, which looks rather elegant, if I do say so myself. So, some sample usage is:
d = RangedDict() d[(1, 15)] = None d[(15, 25)] = "1d6x1000 cp"
Now you can easily use d[4] and get what you want.
The astute reader will however notice that this implementation does not allow you to use tuples as keys at all in your dict. I don't see that as a common use case, or something elegant to do, in general, so I felt it was worth the tradeoff. However, a new class could be made named along the lines of RangeKey(1, 5, step=2) that could be used to key ranges, thus letting you key tuples as well.
I hope a future reader enjoys my code!
None, 15-30 should map to1d6x1000 cp, etc. Thought it was pretty self-explanatory. :( \$\endgroup\$COINSvariable is actually a dictionary ofrange_dicts, but I just thought that would confuse the example since that's pretty irrelevant. But the way I use it in the code is likeCOINS[5][d100()], where5is the Challenge Rating andd100()generates a random number between 1 and 100. \$\endgroup\$