파이썬 - Python의 사전 키 범위




파이썬 이중 딕셔너리 (6)

그래서 저는 사전에 하나의 값을위한 키로 숫자의 범위를 사용할 수 있다는 생각이 들었습니다.

나는 코드를 작성했지만 작동시키지 못한다. 심지어 가능할까요?

    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]

다음은 확률이 고정 된 고정 된 범주 문자열 세트 중 하나에 randint를 매핑하는 데 최대한 효율적입니다.

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

답변 해 주셔서 감사합니다. 나는 해킹을 계속하고 있었고, 제 목표에 아주 잘 맞는 해결책을 찾았습니다. @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.

예, range 목록을 변경 불가능한 tuple 로 변환해야만 해시 가능하고 사전의 키로 허용 될 수 있습니다.

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

편집 : 사실 파이썬 3 range 에서 불변의 시퀀스 형식으로 작동하고 L3viathan 명시된 바와 같이 불변 tuplelist 대신 생성합니다.

하지만 하나의 정수를 키로해서 액세스 할 수는 없습니다. 마지막 줄은 작동하지 않습니다.

나는 값이 무엇이든 상관없이 작동 할 수있는 솔루션을 만들기 위해 시간을 보냈다. 사전이 더 큰 범위로 "가중치"되지 않는 한 사전에서 하나의 엔트리를 선택했다.

그것은 정렬 된 키에서 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]])

이 방법을 사용하면 원하는 결과를 얻을 수 있으며 마지막 행은 작동합니다 ( 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])

BTW 20면 다이를 시뮬레이션하는 경우 최종 인덱스가 20이 아닌 21이어야합니다 (20은 범위 (1,20)에 있지 않으므로).


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 (파이썬 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())

일부는 손에서 실제 숫자 롤을 가져 오는 단점이 있지만 구현 및 유지 관리가 훨씬 간단합니다.

Pythonic

"Pythonic"은 코드를 간단하고 친숙하게 유지하는 것을 의미합니다. 그것은 그들이 의도 한 목적을 위해 구조물을 사용하는 것을 의미합니다. dict 는 당신이하고있는 일을 위해 설계되지 않았습니다.

속도

이 모든 옵션은 비교적 빠릅니다. raratiru 의 comment 에 따르면 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

numpy는 한 번에 하나의 결과를 얻는 경우 느린 순서입니다. 그러나 결과를 일괄 적으로 생성하면 속도가 훨씬 빨라집니다.


range 대신 xrange 를 사용하면 Python 3 및 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)). 그러나 여러분이 보여주는 작은 사전이 괜찮다는 것을 보여 주면. 실제로 그렇게하고 싶다면, 자동으로 작업하도록 서브 클래스를 지정합니다.

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'




range