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\$COINS
variable is actually a dictionary ofrange_dict
s, 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()]
, where5
is the Challenge Rating andd100()
generates a random number between 1 and 100. \$\endgroup\$