我用Python做了一个规模相对较大的项目,作为一个学习实验,在这个过程中,我发现有一张地图(或者,在Python中,我猜它们被称为字典)是有用的,其中一个值可以有多个键。我在那里找不到太多的支持,所以这就是我最后使用的实现:
def range_dict(*args):
return_val = {}
for k,v in args:
for i in k:
return_val[i] = v
return return_val
COINS = range_dict((range(1,15), None),
(range(15,30), "1d6x1000 cp"),
(range(30,53), "1d8x100 sp"),
(range(53,96), "2d8x10 gp"),
(range(96,101), "1d4x10 pp"))有人告诉我,这并不是实现我想做的事情的好方法,但我真的不明白为什么。它在我的应用程序中运行得很好。这是怎么回事?有没有更好的,更毕达通的方法来做到这一点?
发布于 2013-09-28 12:32:02
的注释
下面是实现这个查找表的方法:
from bisect import bisect_left
from collections.abc import Mapping
class LookupTable(Mapping):
"""A lookup table with contiguous ranges of small positive integers as
keys. Initialize a table by passing pairs (max, value) as
arguments. The first range starts at 1, and second and subsequent
ranges start at the end of the previous range.
>>> t = LookupTable((10, '1-10'), (35, '11-35'), (100, '36-100'))
>>> t[10], t[11], t[100]
('1-10', '11-35', '36-100')
>>> t[0]
Traceback (most recent call last):
...
KeyError: 0
>>> next(iter(t.items()))
(1, '1-10')
"""
def __init__(self, *table):
self.table = sorted(table)
self.max = self.table[-1][0]
def __getitem__(self, key):
key = int(key)
if not 1 <= key <= self.max:
raise KeyError(key)
return self.table[bisect_left(self.table, (key,))][1]
def __iter__(self):
return iter(range(1, self.max + 1))
def __len__(self):
return self.max关于这一执行情况的说明:
collections.abc.Mapping抽象基类提供了只读字典的大部分功能。其思想是提供__getitem__、__iter__和__len__方法,Mapping类为您提供__contains__、keys、items、values、get、__eq__和__ne__方法的实现。(如果需要,可以重写,但这里没有必要。)bisect.bisect_left有效地查找了排序表中的密钥。发布于 2015-03-10 19:45:12
所以,我今天早些时候偶然发现了这个问题。对于这里的大多数解决方案,我并不感到非常兴奋,主要是因为我希望能够设置正常的键,但在需要的时候也一次性设置一系列的键,所以我想出了如下方法:
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)它用元组来描述范围,如果我自己这么说的话,它看起来相当优雅。因此,一些示例用法是:
d = RangedDict()
d[(1, 15)] = None
d[(15, 25)] = "1d6x1000 cp"现在您可以很容易地使用d[4]并得到您想要的东西。
然而,精明的读者会注意到,这个实现不允许您在dict中使用元组作为键。我不认为这是一个普通的用例,或者一般的一些优雅的事情,所以我觉得这是值得的。但是,可以按照RangeKey(1, 5, step=2)的方式命名一个新类,它可以用于键范围,因此也允许您使用键元组。
我希望未来的读者喜欢我的代码!
发布于 2013-09-29 01:22:13
这种安排的主要缺点是浪费。对于一个小的例子来说,这不是什么大问题,但是对于一个大范围的值来说,您需要一个同样大的键范围,这意味着需要大量搜索正确的键,同时也要花费内存来存储所有的键。
对于特定的应用程序,python 等分模块非常方便。它对于快速搜索排序的数据很好,所以对于这种查找表来说非常快:
import bisect
# Set up the data. Don't do it inside the function, you don't
# want to do it over and over in the function!
xp_table = [(0, 0), (15, "1d6x1000 cp"), (30, "1d8x100 sp"), (53, "2d8x10 gp"), (96 ,"1d4x10 pp")]
xp_table.sort()
keys = [k[0] for k in xp_table]
def get_reward(val):
idx = bisect.bisect_right(keys, val)
return xp_table[idx][1]基于字典的解决方案对于不排序或不属于整洁范围的数据仍然会更好。
https://codereview.stackexchange.com/questions/31907
复制相似问题