首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >“多键”字典

“多键”字典
EN

Code Review用户
提问于 2013-09-27 16:53:08
回答 6查看 17.6K关注 0票数 11

我用Python做了一个规模相对较大的项目,作为一个学习实验,在这个过程中,我发现有一张地图(或者,在Python中,我猜它们被称为字典)是有用的,其中一个值可以有多个键。我在那里找不到太多的支持,所以这就是我最后使用的实现:

代码语言:javascript
复制
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"))

有人告诉我,这并不是实现我想做的事情的好方法,但我真的不明白为什么。它在我的应用程序中运行得很好。这是怎么回事?有没有更好的,更毕达通的方法来做到这一点?

EN

回答 6

Code Review用户

回答已采纳

发布于 2013-09-28 12:32:02

1.对代码

的注释

  1. 没有文件串!你的代码是做什么的,我应该如何使用它?
  2. 没有测试用例。这应该是一个使用博士考试的好机会。
  3. 考虑到您在这里的目的是实现RPG查找表,您的设计似乎有点难以使用。印刷的RPG表可能是这样的: 1-8巨型蜘蛛9-12骨架13-18黄铜龙.但是你必须像这样输入它:range_dict(范围(1,9),“巨型蜘蛛”),(范围(9,13),“骨架”),(范围(13,18),“黄铜龙”),.)这很容易出错。
  4. 这个设计看起来也很脆弱,因为它不能保证范围是连续的。如果您犯了数据输入错误:d=range_dict(范围(1,8),“巨型蜘蛛”),(范围(9,13),“骨架”),(范围(13,18),“黄铜龙”),.)之后,缺少的键将导致一个错误:>>> d8跟踪(最近一次调用): KeyError: 8中的"“文件,第1行。

2.另一种方法

下面是实现这个查找表的方法:

代码语言:javascript
复制
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

关于这一执行情况的说明:

  1. 构造函数对每个范围取最大值(而不是最大值加一个),使数据输入不太容易出错。
  2. 每个范围的最小值是从前一个范围结束时开始的,从而确保范围之间没有差距。
  3. collections.abc.Mapping抽象基类提供了只读字典的大部分功能。其思想是提供__getitem____iter____len__方法,Mapping类为您提供__contains__keysitemsvaluesget__eq____ne__方法的实现。(如果需要,可以重写,但这里没有必要。)
  4. 我使用bisect.bisect_left有效地查找了排序表中的密钥。
票数 9
EN

Code Review用户

发布于 2015-03-10 19:45:12

所以,我今天早些时候偶然发现了这个问题。对于这里的大多数解决方案,我并不感到非常兴奋,主要是因为我希望能够设置正常的键,但在需要的时候也一次性设置一系列的键,所以我想出了如下方法:

代码语言:javascript
复制
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)

它用元组来描述范围,如果我自己这么说的话,它看起来相当优雅。因此,一些示例用法是:

代码语言:javascript
复制
d = RangedDict()
d[(1, 15)] = None
d[(15, 25)] = "1d6x1000 cp"

现在您可以很容易地使用d[4]并得到您想要的东西。

然而,精明的读者会注意到,这个实现不允许您在dict中使用元组作为键。我不认为这是一个普通的用例,或者一般的一些优雅的事情,所以我觉得这是值得的。但是,可以按照RangeKey(1, 5, step=2)的方式命名一个新类,它可以用于键范围,因此也允许您使用键元组。

我希望未来的读者喜欢我的代码!

票数 6
EN

Code Review用户

发布于 2013-09-29 01:22:13

这种安排的主要缺点是浪费。对于一个小的例子来说,这不是什么大问题,但是对于一个大范围的值来说,您需要一个同样大的键范围,这意味着需要大量搜索正确的键,同时也要花费内存来存储所有的键。

对于特定的应用程序,python 等分模块非常方便。它对于快速搜索排序的数据很好,所以对于这种查找表来说非常快:

代码语言:javascript
复制
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]

基于字典的解决方案对于不排序或不属于整洁范围的数据仍然会更好。

票数 4
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/31907

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档