问题描述
x = {(0,4): 'foo',(4,9): 'bar',(9,10): 'sheep'}
任务是编写函数,找到给定特定数字的值,例如如果用户输入 3,它应该返回 'foo'
。我们可以假设密钥中没有重叠的数字。
我尝试将 x
dict 转换为列表并按如下方式编写函数,但如果键中的值范围非常大,则它不是最佳的:
from itertools import chain
mappings = None * max(chain(*x))
for k in x:
for i in range(k[0],k[1]):
mappings[i] = x[k]
def myfunc(num):
return mapping[num]
- 还可以如何编写
myfunc
函数? - 是否有更好的数据结构来保留
mapping
?
解决方法
您可以简单地遍历键并比较值(而不是创建映射)。这比先创建映射要高效一些,因为您可以使用 (0,100000)
这样的键,这会产生不必要的开销。
根据 OP 的评论编辑答案
x = {(0,4): 'foo',(4,9): 'bar',(9,10): 'sheep'}
def find_value(k):
for t1,t2 in x:
if k > t1 and k <= t2: # edited based on comments
return x[(t1,t2)]
# if we end up here,we can't find a match
# do whatever appropriate,e.g. return None or raise exception
return None
注意: 不清楚您的元组键是否包含输入数字的范围。例如。如果用户输入 (请参阅上面的编辑,这应该满足您的要求)。4
,他们应该得到 'foo'
还是 'bar'
?这将影响您在我的代码段中上述功能的比较。
在上面的这个例子中,输入 4
将返回 'foo'
,因为它满足 k >= 0 and k <= 4
的条件,因此在继续循环之前返回。
编辑:措辞和错别字修正
,您可以将键转换为 numpy
数组并使用 numpy.searchsorted
来搜索查询。由于键是 left open
,因此我将数组中键的打开值增加了 1
。
每个查询的顺序为 O(log(n))
。
创建一个数组:
A = np.array([[k1+1,k2] for k1,k2 in x])
>>> A
array([[ 1,4],[ 5,9],[10,10]])
搜索查询的功能:
def myfunc(num):
ind1 = np.searchsorted(A[:,0],num,'right')
ind2 = np.searchsorted(A[:,1],'left')
if ind1 == 0 or ind2 == A.shape[0] or ind1 <= ind2: return None
return vals[ind2]
打印:
>>> myfunc(3)
'foo'
,
迭代字典与键的比较:
x = {(0,10): 'sheep'}
def find_tuple(dct,num):
for tup,val in dct.items():
if tup[0] <= num < tup[1]:
return val
return None
print(find_tuple(x,3))
# foo
print(find_tuple(x,9))
# sheep
print(find_tuple(x,11))
# None
一个更好的数据结构是一个字典,只有区间的左边界(作为键)和相应的值。然后您可以使用 bisect
作为其他答案提及。
import bisect
import math
x = {
-math.inf: None,0: 'foo',4: 'bar',9: 'sheep',10: None,}
def find_tuple(dct,num):
idx = bisect.bisect_right(list(dct.keys()),num)
return list(dct.values())[idx-1]
print(find_tuple(x,11))
# None
,
这是使用 pandas.IntervalIndex
和 pandas.cut
的一种解决方案。请注意,我将最后一个键“调整”为 (10,11),因为我在 IntervalIndex 中使用了 closed="left"
。如果您希望间隔在不同侧(或两侧)闭合,您可以更改此设置:
import pandas as pd
x = {(0,4): "foo",9): "bar",(10,11): "sheep"}
bins = pd.IntervalIndex.from_tuples(x,closed="left")
result = pd.cut([3],bins)[0]
print(x[(result.left,result.right)])
打印:
foo
使用 bisect
模块的其他解决方案(假设范围是连续的 - 所以没有“间隙”):
from bisect import bisect_left
x = {(0,10): "sheep"}
bins,values = [],[]
for k in sorted(x):
bins.append(k[1]) # intervals are closed "right",eg. (0,4]
values.append(x[k])
idx = bisect_left(bins,4)
print(values[idx])
打印:
foo