web-dev-qa-db-fra.com

Range comme clé de dictionnaire en Python

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]
16
Cuylar Conly

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'
11
L3viathan

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.']
6
Mark Ransom

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

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'

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

3
Menglong Li

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

2
Paul Cornelius
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]
2
TheLazyScripter

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]])
1
Terry Jan Reedy