Python: 区间映射对应的值

25

我正在重构一个函数,该函数给定一系列隐含定义间隔的端点,检查数字是否包含在间隔内,然后返回相应的值(与任何可计算方式无关)。 当前处理此任务的代码为:

if p <= 100:
    return 0
elif p > 100 and p <= 300:
    return 1
elif p > 300 and p <= 500:
    return 2
elif p > 500 and p <= 800:
    return 3
elif p > 800 and p <= 1000:
    return 4
elif p > 1000:
    return 5

在我看来,这相当糟糕,因为区间和返回值都是硬编码的。当然可以使用任何数据结构。

6个回答

56
import bisect
bisect.bisect_left([100,300,500,800,1000], p)

这里是文档:bisect


3
真的很棒。非常简洁,我相信速度也很快。 如果需要非自然排序或其他返回内容(如字符串),它也可以很容易地进行扩展: import bisect n = bisect.bisect_left([100,300,500,800,1000], p) a=["缺席","较低","一般","较高","很高","极高"] a[n] - Agos
嗯,但这并不返回“任意值”,它返回索引。我该如何使其返回任意值?我尝试了 p = 10 x = bisect.bisect_left(OrderedDict({10: 'a', 11: 'b'}), p) print() 但它没有起作用。 - Charlie Parker

3
你可以尝试以下方法:

您可以尝试以下方法:

def check_mapping(p):
    mapping = [(100, 0), (300, 1), (500, 2)] # Add all your values and returns here

    for check, value in mapping:
        if p <= check:
            return value

print check_mapping(12)
print check_mapping(101)
print check_mapping(303)

生成:

0
1
2

像往常一样,在Python中,总会有更好的方法来完成它。


不考虑 p > 1000 的情况! - stefanw
这就是为什么我指定了:“你可以试着尝试一下这个”。 - kjfletch
考虑到 Python 的哲学是尽可能只有一种明显的方法来完成某件事,那最后一句话真是讽刺。 - sykora
BUG: 如果p大于最后一个端点,则会产生None。 - John Machin

3

这确实很糟糕。如果没有不得硬编码的要求,应该像这样编写:

if p <= 100:
    return 0
elif p <= 300:
    return 1
elif p <= 500:
    return 2
elif p <= 800:
    return 3
elif p <= 1000:
    return 4
else:
    return 5

以下是创建查找函数的示例,包括线性查找和使用二进制搜索,同时满足无硬编码要求,并对两个表进行了一些合理性检查:
def make_linear_lookup(keys, values):
    assert sorted(keys) == keys
    assert len(values) == len(keys) + 1
    def f(query):
        return values[sum(1 for key in keys if query > key)]
    return f

import bisect
def make_bisect_lookup(keys, values):
    assert sorted(keys) == keys
    assert len(values) == len(keys) + 1
    def f(query):
        return values[bisect.bisect_left(keys, query)]
    return f

我喜欢这个比那个得票最高的更好,因为它具有更一般化/非硬编码形式,并且更加深入。 - JAB

0
尝试以下类似的内容:
d = {(None,100): 0, 
    (100,200): 1,
    ...
    (1000, None): 5}
value = 300 # example value
for k,v in d.items():
    if (k[0] is None or value > k[0]) and (k[1] is None or value <= k[1]):
        return v

0
def which_interval(endpoints, number):
    for n, endpoint in enumerate(endpoints):
        if number <= endpoint:
            return n
        previous = endpoint
    return n + 1

将您的端点作为列表传递给endpoints,如下所示:

which_interval([100, 300, 500, 800, 1000], 5)

编辑:

以上是线性搜索。Glenn Maynard的答案将具有更好的性能,因为它使用了二分算法。


去掉“previous”这个词,它非常冗余。 - John Machin
是的,你说得对,我想原始代码“启发”了我使用它。顺便说一下,你使用祈使语气可能会听起来有点粗鲁。 - Steef
@Steef:您可能想考虑一个谦虚的建议,即在您有空的时候重新审视您的答案,注意到您的答案仍然包含一行冗余代码,并在适当的时候删除它。 - John Machin

0

另一种方法...

def which(lst, p): 
    return len([1 for el in lst if p > el])

lst = [100, 300, 500, 800, 1000]
which(lst, 2)
which(lst, 101)
which(lst, 1001)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接