そのため、辞書の単一の値のキーとして、ある範囲の数値を使用できるという考えがありました。
以下のコードを書きましたが、動作させることができません。それも可能ですか?
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]
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'
範囲自体をキーにしない限り、範囲から直接辞書を作成することはできません。私はあなたがそれを望んでいるとは思わない。範囲内の可能性ごとに個別のエントリを取得するには:
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.']
はい、できます。range
リストを不変のTuple
として変換する場合のみ、ハッシュ可能であり、辞書のキーとして受け入れられます。
_stealth_check = {
Tuple(range(1, 6)) : 'You are about as stealthy as thunderstorm.',
_
編集:実際には、Python 3 range
は不変のシーケンス型であり、L3viathanとしてTuple
の代わりに不変のlist
を生成します]述べました。
ただし、単一の整数をキーとしてそれらにアクセスすることはできません。最後の行は機能しません。
値に関係なく機能するソリューションを作成するのに少し時間をかけました(辞書内の1つのエントリを選択しても、行がより大きな範囲で「重み付け」されていない限り機能します)。
ソートされたキーで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]])
_
このようなケースを処理するために、より一般的で使いやすい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'
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))
このアプローチには何の問題もありません。本当にこれ以上複雑である必要はありません。これはmuchここでdict
を使用しようとするよりも直感的で、理解しやすく、はるかに効率的です。
この方法で行うと、境界処理がより見やすくなります。上記のコードでは、範囲で<
または<=
各場所。上記のコードは、1〜20以外の値に対して意味のあるエラーメッセージもスローします。また、整数以外の入力を無料でサポートしますが、気にする必要はありません。
キーの範囲を使用しようとする代わりに、特定のキーを特定の値にマッピングするdoesに問題を再定式化できます。そのためには、範囲をループして、可能なすべての値を含む完全な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
を適切に使用します。キーを値にマップします。
あなたはcould確率計算に基づいて結果を選択します。基本的な考え方は、「累積」確率(ロール値の上端で既に持っている)を計算し、累積確率がランダム値を超えるまでループスルーすることです。その方法についてのアイデアはたくさんあります こちら 。
いくつかの簡単なオプションは次のとおりです。
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())
実際の数値をロールアウトするというデメリットもありますが、実装と保守がはるかに簡単です。
「Pythonic」とは、コードをわかりやすく親しみやすいものにすることを意味します。それは、設計された目的に構造を使用することを意味します。 dict
はあなたがしていることのために設計されたものではありません。
これらのオプションはすべて比較的高速です。 raratir の 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は、一度に1つの結果しか得られない場合、1桁遅くなります。ただし、結果を大量に生成する場合は、桁違いに速くなります。
このアプローチはあなたが望むものを達成し、最後の行が機能します(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])
ところで、20面のダイをシミュレートする場合、最終インデックスは20ではなく21にする必要があります(20はrange(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]
以下は、確率を固定した固定カテゴリ文字列のセットの1つに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]])