J'ai donc eu l'idée d'utiliser une plage de nombres comme clé pour une valeur unique dans un dictionnaire.
J'ai écrit le code ci-dessous, mais je ne parviens pas à le faire fonctionner. Est-ce même possible?
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]
C'est possible sur Python 3 - et sur Python 2 si vous utilisez xrange
au lieu de range
:
stealth_check = {
xrange(1, 6) : 'You are about as stealthy as thunderstorm.', #...
}
Cependant, la façon dont vous essayez de l'utiliser ne fonctionnera pas. Vous pouvez parcourir les clés, comme ceci:
for key in stealth_check:
if stealth_roll in key:
print stealth_check[key]
break
La performance de ce n'est pas Nice (O (n)) mais si c'est un petit dictionnaire comme vous l'avez montré, ça va. Si vous voulez réellement faire cela, je voudrais sous-classe dict
fonctionner comme ça automatiquement:
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'
Vous ne pouvez pas créer un dictionnaire directement à partir d'une plage, sauf si vous souhaitez que la plage elle-même soit la clé. Je ne pense pas que tu veux ça. Pour obtenir des entrées individuelles pour chaque possibilité dans la plage:
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)]
)
Lorsque vous avez une dict
indexée par une petite plage d'entiers, vous devriez vraiment envisager d'utiliser une 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.']
Oui, vous pouvez le faire, mais uniquement si vous convertissez vos listes range
en immuable Tuple
, de sorte qu'elles soient hashable et acceptées comme clés de votre dictionnaire:
stealth_check = {
Tuple(range(1, 6)) : 'You are about as stealthy as thunderstorm.',
EDIT: en fait, cela fonctionne dans Python 3, car range
est un type de séquence immuable et génère une Tuple
immuable au lieu d'une list
comme indiqué par L3viathan.
mais vous ne pouvez pas y accéder avec un seul entier comme clé. Votre dernière ligne ne fonctionnera pas.
J'ai pris un peu de temps pour créer une solution qui fonctionnerait quelles que soient les valeurs (choisir une entrée dans le dictionnaire fonctionne tant que les lignes ne sont pas "pondérées" par des plages plus grandes.
Il appelle bisect
sur les clés triées pour trouver le point d’insertion, le hache un peu et trouve la meilleure valeur dans le dictionnaire, avec la complexité O(log(N))
, ce qui signifie qu’il peut gérer une très grande liste (peut-être un peu trop ici :) mais le dictionnaire est aussi trop dans ce cas)
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]])
J'ai écrit une classe RangeKeyDict pour gérer des cas comme celui-ci, qui est plus général et facile à utiliser. Pour l’utilisation, vérifiez les codes dans __main__
pour l'installer en utilisant:
pip install range-key-dict
Usage:
from range_key_dict import RangeKeyDict
if __== '__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'
Cette approche accomplira ce que vous voulez et la dernière ligne fonctionnera (en supposant un comportement Py3 de range
et print
):
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])
BTW si vous simulez un dé à 20 faces, vous avez besoin que l’indice final soit 21, pas 20 (puisque 20 n’est pas dans la fourchette (1,20)).
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]
Ce qui suit est probablement extrêmement efficace pour mapper un randint sur l’un des ensembles de chaînes de catégories fixes avec une probabilité fixe.
from random import randint
stealth_map = (None, 0,0,0,0,0,0,1,1,1,1,1,2,2,2,2,2,3,3,3,3)
stealth_type = (
'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.',
)
for i in range(10):
stealth_roll = randint(1, 20)
print(stealth_type[stealth_map[stealth_roll]])