范围作为Python中的字典键

时间:2016-09-06 21:25:26

标签: 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]

10 个答案:

答案 0 :(得分:14)

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

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

然而,您尝试使用它的方式却无法奏效。您可以迭代键,如下所示:

for key in stealth_check:
    if stealth_roll in key:
        print stealth_check[key]
        break

这不是很好的表现(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]
        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'

答案 1 :(得分: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)]
                    )

如果你的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.']

答案 2 :(得分:6)

是的,只有当您将range列表转换为不可变tuple时才可以,因此它们可以播放并作为词典的键接受:

stealth_check = {
                tuple(range(1, 6)) : 'You are about as stealthy as thunderstorm.',

编辑:实际上它在Python 3中有效,因为range是一个不可变的序列类型,并且生成一个不可变的tuple,而不是像L3viathan所说的那样list

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

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

它在排序的键上调用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]])

答案 3 :(得分:3)

我编写了一个RangeKeyDict类来处理这样的情况,它更通用且易于使用。如需使用,请检查__main __

中的代码

使用以下方式安装它:

pip install range-key-dict

用法:

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'

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

答案 4 :(得分:3)

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))

这种方法绝对没有错。它真的不需要更复杂。这比 更直观,更容易理解,并且比在此处使用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))

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

根据概率计算

可以根据概率计算选择结果。基本思想是计算“累积”概率(您已经在滚动值的顶端已经存在),然后循环直到累积概率超过随机值。有很多关于如何实现它的想法here

一些简单的选项是:

  • 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))
    
  • 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())
    

有些人有把握实际数字卷的缺点,但实施和维护起来要简单得多。

Python化

“Pythonic”意味着保持您的代码简单易用。它意味着将结构用于其设计目的。 dict并非专为你正在做的事而设计。

速度

所有这些选项都相对较快。根据{{​​3}}的raratiruRangeDict是当时最快的答案。但是,我的comment显示除了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
如果从中获得一个结果,那么numpy会慢一个数量级;但是,如果您批量生成结果,它会快一个数量级。

答案 5 :(得分:2)

stealth_check = {
                    0 : 'You are about as stealthy as thunderstorm.',
                    1 : 'You tip-toe through the crowd of walkers, while loudly calling them names.',
                    2 : 'You are quiet, and deliberate, but still you smell.',
                    3 : 'You move like a ninja, but attracting a handful of walkers was inevitable.'
                    }
stealth_roll = randint(0, len(stealth_check))
return stealth_check[stealth_roll]

答案 6 :(得分:2)

这种方法可以达到你想要的效果,最后一行可行(假设rangeprint的Py3行为):

def extend_dict(d, value, x):
    for a in x:
        d[a] = value

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

print(stealth_check[stealth_roll])

顺便说一句,如果你要模拟一个20边的骰子,你需要最终的指数为21而不是20(因为20不在范围内(1,20))。

答案 7 :(得分:1)

以下可能是最有效的将randint映射到具有固定概率的一组固定类别字符串之一。

$start = strtotime('2016-09-05');
$end = strtotime('2016-09-30');

while($start <= $end) {
    if(date("D", $start) == "Mon"){
        $mondays[] = $start;
    }
    elseif(date("D", $start) == "Tue"){
        $tuesdays[] = $start;
    }
    $start += 86400;
}

foreach ($mondays as $item){
    echo date("D. Y-m-d", $item);
    echo "<br>";
}

echo "<br>";

foreach ($tuesdays as $item){
    echo date("D. Y-m-d", $item);
    echo "<br>";
}

答案 8 :(得分:0)

我参加聚会可能会迟到,但是在这里我如何解决类似的问题。

import bisect

outcomes = ["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."]
ranges = [6, 11, 16]

outcome_index = bisect.bisect(ranges, 20)
print(outcomes[outcome_index])

答案 9 :(得分:-1)

感谢大家的回复。我一直在乱砍,我提出了一个很适合我的目的的解决方案。这与@PaulCornelius的建议最为相似。

stealth_roll = randint(1, 20)
# select from a dictionary of 4 responses using one of four ranges.
# only one resolution can be True. # True can be a key value.

def check(i, a, b): # check if i is in the range. # return True or False
    if i in range(a, b):
        return True
    else:
        return False
### can assign returned object as dictionary key! # assign key as True or False.
stealth_check = {
                check(stealth_roll, 1, 6) : 
                'You are about as stealthy as a thunderstorm.',
                check(stealth_roll, 6, 11) : 
                'You tip-toe through the crowd of walkers, while loudly calling them names.',
                check(stealth_roll, 11, 16) : 
                'You are quiet, and deliberate, but still you smell.',
                check(stealth_roll, 15, 21) : 
                'You move like a ninja, but attracting a handful of walkers was inevitable.'
                }

print stealth_check[True] # print the dictionary value that is True.