范围作为Python中的字典键

Cuy*_*nly 23 python dictionary range

所以,我有一个想法,我可以使用一系列数字作为字典中单个值的键.

我写了下面的代码,但我无法让它工作.它甚至可能吗?

    stealth_roll = randint(1, 20)
    # select from a dictionary of 4 responses using one of four ranges.
    ## not working.
    stealth_check = {
                    range(1, 6) : 'You are about as stealthy as thunderstorm.',
                    range(6, 11) : 'You tip-toe through the crowd of walkers, while loudly calling them names.',
                    range(11, 16) : 'You are quiet, and deliberate, but still you smell.',
                    range(16, 20) : 'You move like a ninja, but attracting a handful of walkers was inevitable.'
                    }

    print stealth_check[stealth_roll]
Run Code Online (Sandbox Code Playgroud)

L3v*_*han 16

它可以在Python 3上使用 - 如果你使用Python 2 xrange而不是range:

stealth_check = {
                xrange(1, 6) : 'You are about as stealthy as thunderstorm.', #...
                }
Run Code Online (Sandbox Code Playgroud)

但是,您尝试使用它的方式将无法正常工作.您可以迭代键,如下所示:

for key in stealth_check:
    if stealth_roll in key:
        print stealth_check[key]
        break
Run Code Online (Sandbox Code Playgroud)

这样的表现并不好(O(n)),但如果它是一个像你所说的小字典就可以了.如果你真的想这样做,我会dict自动子类化工作:

class RangeDict(dict):
    def __getitem__(self, item):
        if type(item) != range: # or xrange in Python 2
            for key in self:
                if item in key:
                    return self[key]
            raise KeyError(item)
        else:
            return super().__getitem__(item)

stealth_check = RangeDict({range(1,6): 'thunderstorm', range(6,11): 'tip-toe'})
stealth_roll = 8
print(stealth_check[stealth_roll]) # prints 'tip-toe'
Run Code Online (Sandbox Code Playgroud)

  • 使用ipython```timeit```来测量这些数据的```RangeDict```的执行时间,证明它是迄今为止提到的最快的技术:```最好的3 :每个循环6.47μs,最慢的运行速度比最快的's`长6.15倍,而最好的其他技术返回的数字如"最佳3:17μs/循环",最慢运行时间长20倍. fastest``` (2认同)
  • 请注意,您已经修改了“dict”的行为。如果找不到非“范围”键,它不再抛出错误。我会在循环后面添加一个“raise”。 (2认同)

jpm*_*c26 10

dict是这项工作的错误工具。dict用于将特定键映射到特定值。那不是你在做什么;您正在尝试映射范围。这里有一些更直接的选项。

使用if

对于一小部分值,请使用明显且直接的if块:

def get_stealthiness(roll):
    if 1 <= roll < 6:
        return 'You are about as stealthy as thunderstorm.'
    elif 6 <= roll < 11:
        return 'You tip-toe through the crowd of walkers, while loudly calling them names.'
    elif 11 <= roll < 16:
        return 'You are quiet, and deliberate, but still you smell.'
    elif 16 <= roll <= 20:
        return 'You move like a ninja, but attracting a handful of walkers was inevitable.'
    else:
        raise ValueError('Unsupported roll: {}'.format(roll))

stealth_roll = randint(1, 20)
print(get_stealthiness(stealth_roll))
Run Code Online (Sandbox Code Playgroud)

这种方法绝对没有错。它真的不需要更复杂。这是很多更直观,比试图用一个更容易弄清楚,并更有效dict这里。

这样做也使边界处理更加明显。在我上面提供的代码中,您可以快速发现范围是否使用<<=在每个地方使用。上面的代码还为 1 到 20 之外的值抛出有意义的错误消息。它也免费支持非整数输入,尽管您可能不关心这一点。

将每个值映射到结果

而不是试图使用范围的钥匙,你可以重新制定您的问题变成一个映射特定键为特定值。您可以通过遍历范围并生成dict包含所有可能值的完整内容来实现:

OUTCOMES = {}
for i in range(1, 6):
    OUTCOMES[i] = 'You are about as stealthy as thunderstorm.'
for i in range(6, 11):
    OUTCOMES[i] = 'You tip-toe through the crowd of walkers, while loudly calling them names.'
for i in range(11, 16):
    OUTCOMES[i] = 'You are quiet, and deliberate, but still you smell.'
for i in range(16, 21):
    OUTCOMES[i] = 'You move like a ninja, but attracting a handful of walkers was inevitable.'

def get_stealthiness(roll):
    if roll not in OUTCOMES.keys():
        raise ValueError('Unsupported roll: {}'.format(roll))
    return OUTCOMES[roll]

stealth_roll = randint(1, 20)
print(get_stealthiness(stealth_roll))
Run Code Online (Sandbox Code Playgroud)

在这种情况下,我们使用范围来生成dict我们可以在其中查找结果的 a。我们将每个滚动映射到一个结果,多次重复使用相同的结果。这不那么简单;从中辨别每个结果的概率并不那么容易。但至少它使用dict得当:它将一个键映射到一个值。

根据概率计算

可以根据概率计算选择结果。基本思想是计算“累积”概率(滚动值的顶端已经有了),然后循环直到累积概率超过随机值。有大量的如何去了解它的想法在这里

一些简单的选项是:

  • numpy.random.choice
  • 一个循环:

    # Must be in order of cummulative weight
    OUTCOME_WITH_CUM_WEIGHT = [
        ('You are about as stealthy as thunderstorm.', 5),
        ('You tip-toe through the crowd of walkers, while loudly calling them names.', 10),
        ('You are quiet, and deliberate, but still you smell.', 15),
        ('You move like a ninja, but attracting a handful of walkers was inevitable.', 20),
    ]
    
    def get_stealthiness(roll):
        if 1 > roll or 20 < roll:
            raise ValueError('Unsupported roll: {}'.format(roll))
        for stealthiness, cumweight in OUTCOME_WITH_CUM_WEIGHT:
            if roll <= cumweight:
                return stealthiness
        raise Exception('Reached end of get_stealthiness without returning. This is a bug. roll was ' + str(roll))
    
    stealth_roll = randint(1, 20)
    print(get_stealthiness(stealth_roll))
    
    Run Code Online (Sandbox Code Playgroud)
  • random.choices (需要 Python 3.6 或更高版本)

    OUTCOMES_SENTENCES = [
        'You are about as stealthy as thunderstorm.',
        'You tip-toe through the crowd of walkers, while loudly calling them names.',
        'You are quiet, and deliberate, but still you smell.',
        'You move like a ninja, but attracting a handful of walkers was inevitable.',
    ]
    OUTCOME_CUMULATIVE_WEIGHTS = [5, 10, 15, 20]
    
    def make_stealth_roll():
        return random.choices(
            population=OUTCOMES_SENTENCES,
            cum_weights=OUTCOME_CUMULATIVE_WEIGHTS,
        )
    
    print(make_stealth_roll())
    
    Run Code Online (Sandbox Code Playgroud)

有些缺点是不需要实际的数字滚动,但它们的实现和维护要简单得多。

蟒蛇式

“Pythonic”意味着让你的代码简单易懂。这意味着将结构用于其设计目的。dict不是为你正在做的事情而设计的。

速度

所有这些选项都相对较快。根据raratiru评论,这RangeDict是当时最快的答案。但是,我的测试脚本显示,除了numpy.random.choice,我建议的所有选项都快了大约 40% 到 50%:

get_stealthiness_rangedict(randint(1, 20)): 3.4458323369617574 µs per loop
get_stealthiness_ifs(randint(1, 20)): 1.8013543629786 µs per loop
get_stealthiness_dict(randint(1, 20)): 1.9512669100076891 µs per loop
get_stealthiness_cumweight(randint(1, 20)): 1.9908560069743544 µs per loop
make_stealth_roll_randomchoice(): 2.037966169009451 µs per loop
make_stealth_roll_numpychoice(): 38.046008297998924 µs per loop
numpy.choice all at once: 0.5016623589908704 µs per loop
Run Code Online (Sandbox Code Playgroud)

如果你一次得到一个结果,numpy 会慢一个数量级;但是,如果您批量生成结果,速度会快一个数量级。

  • 因为这不是要求的。学习编程的一部分是尝试语言的特性。因此,即使 OP 解决方案太复杂,也并非总是重点。一个不那么居高临下的回答也可能有所帮助。 (3认同)

Mar*_*som 7

除非您希望范围本身成为关键,否则无法直接从范围构建字典.我不认为你想要那个.要获得范围内每种可能性的单独条目:

stealth_check = dict(
                    [(n, 'You are about as stealthy as thunderstorm.')
                        for n in range(1, 6)] +
                    [(n, 'You tip-toe through the crowd of walkers, while loudly calling them names.')
                        for n in range(6, 11)] +
                    [(n, 'You are quiet, and deliberate, but still you smell.')
                        for n in range(11, 16)] +
                    [(n, 'You move like a ninja, but attracting a handful of walkers was inevitable.')
                        for n in range(16, 20)]
                    )
Run Code Online (Sandbox Code Playgroud)

如果你有一个dict小范围的整数索引,你真的应该考虑使用一个list代替:

stealth_check = [None]
stealth_check[1:6] = (6 - 1) * ['You are about as stealthy as thunderstorm.']
stealth_check[6:11] = (11 - 6) * ['You tip-toe through the crowd of walkers, while loudly calling them names.']
stealth_check[11:16] = (16 - 11) * ['You are quiet, and deliberate, but still you smell.']
stealth_check[16:20] = (20 - 16) * ['You move like a ninja, but attracting a handful of walkers was inevitable.']
Run Code Online (Sandbox Code Playgroud)


Jea*_*bre 6

是的,只有当您将range列表转换为不可变列表时才可以,tuple因此它们可以清除并被接受为字典的键:

stealth_check = {
                tuple(range(1, 6)) : 'You are about as stealthy as thunderstorm.',
Run Code Online (Sandbox Code Playgroud)

编辑:实际上它在Python 3中工作,因为它range是一个不可变的序列类型,并生成一个不可变的tuple而不是一个listL3viathan声明.

但是你不能用一个整数作为键来访问它们.你的最后一行不起作用.

我花了一些时间来创建一个无论值是什么都可以工作的解决方案(只要这些行没有被更大的范围"加权",就可以在字典中选择一个条目.

它调用bisect排序键来查找插入点,稍微破解它,并在字典中找到最佳值,具有O(log(N))复杂性,这意味着它可以处理一个非常大的列表(可能有点太多了:)但是字典在这种情况下也是太多了)

from random import randint
import bisect

stealth_roll = randint(1, 20)
# select from a dictionary of 4 responses using one of four thresholds.

stealth_check = {
                1 : 'You are about as stealthy as thunderstorm.',
                6 : 'You tip-toe through the crowd of walkers, while loudly calling them names.',
                11 : 'You are quiet, and deliberate, but still you smell.',
                16 : 'You move like a ninja, but attracting a handful of walkers was inevitable.'
                }

sorted_keys = sorted(stealth_check.keys())


insertion_point = bisect.bisect_left(sorted_keys,stealth_roll)

# adjust, as bisect returns not exactly what we want
if insertion_point==len(sorted_keys) or sorted_keys[insertion_point]!=stealth_roll:
    insertion_point-=1

print(insertion_point,stealth_roll,stealth_check[sorted_keys[insertion_point]])
Run Code Online (Sandbox Code Playgroud)

  • 当你回答:)固定时,你必须要迂腐.谢谢. (4认同)
  • 也许这很迂腐但是`range`不是Python 3中的生成器函数. (2认同)

Men*_* Li 6

我编写了一个 RangeKeyDict 类来处理这样的情况,它更通用且易于使用。使用方法请查看__main__中的代码

使用以下命令安装它:

pip install range-key-dict
Run Code Online (Sandbox Code Playgroud)

用法:

from range_key_dict import RangeKeyDict

if __name__ == '__main__':
    range_key_dict = RangeKeyDict({
        (0, 100): 'A',
        (100, 200): 'B',
        (200, 300): 'C',
    })

    # test normal case
    assert range_key_dict[70] == 'A'
    assert range_key_dict[170] == 'B'
    assert range_key_dict[270] == 'C'

    # test case when the number is float
    assert range_key_dict[70.5] == 'A'

    # test case not in the range, with default value
    assert range_key_dict.get(1000, 'D') == 'D'
Run Code Online (Sandbox Code Playgroud)

https://github.com/albertmenglongli/range-key-dict

  • 问题的时间复杂度是 O(log(N)),而算法的时间复杂度是 O(N)。换句话说,您的解决方案的扩展速度将不理想。 (6认同)