# 파이썬 - 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 명시된 바와 같이 불변 `tuple``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]])``````

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