为啥范围(开始,结束)不包括结束?

Posted

技术标签:

【中文标题】为啥范围(开始,结束)不包括结束?【英文标题】:Why does range(start, end) not include end?为什么范围(开始,结束)不包括结束? 【发布时间】:2011-05-29 03:03:04 【问题描述】:
>>> range(1,11)

给你

[1,2,3,4,5,6,7,8,9,10]

为什么不是1-11?

他们只是随意决定这样做还是有一些我没有看到的价值?

【问题讨论】:

阅读 Dijkstra, ewd831 基本上,您是在为另一组单独选择一组错误。一组更有可能导致您的循环提前终止,另一组可能导致异常(或其他语言中的缓冲区溢出)。一旦你编写了一堆代码,你就会发现range() 行为的选择更有意义 链接到 Dijkstra,ewd831:cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF @unutbu Djikstra 的那篇文章在这个话题中经常被引用,但在这里没有提供任何有价值的东西,人们只是把它用作对权威的呼吁。他为 OP 的问题给出的唯一相关的伪原因是他碰巧觉得在 特殊情况 中包含上限变得“不自然”和“丑陋”,其中序列为空 - 这完全是主观立场,容易反对,所以它不会带来太多好处。如果不了解 Mesa 的特定约束或评估方法,那么使用 Mesa 进行的“实验”也没有多大价值。 @andreasdr 但是即使表面上的论点是有效的,Python 的方法不是引入了新的可读性问题吗?在通用英语中,术语“范围”意味着某事物的范围某事物某事物——就像一个区间。 len(list(range(1,2))) 返回 1 和 len(list(range(2))) 返回 2 是你真正必须学会消化的东西。 【参考方案1】:

因为调用range(0, 10) 更常见,它返回[0,1,2,3,4,5,6,7,8,9],其中包含10 个等于len(range(0, 10)) 的元素。请记住,程序员更喜欢从 0 开始的索引。

另外,请考虑以下通用代码 sn-p:

for i in range(len(li)):
    pass

你能看出如果range() 正好上升到len(li) 这会有问题吗?程序员需要显式减 1。这也符合程序员更喜欢 for(int i = 0; i < 10; i++) 而不是 for(int i = 0; i <= 9; i++) 的普遍趋势。

如果您经常调用以 1 开头的范围,您可能需要定义自己的函数:

>>> def range1(start, end):
...     return range(start, end+1)
...
>>> range1(1, 10)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

【讨论】:

如果这是推理,参数不是range(start, count)吗? @shogun 起始值默认为0,即range(10)等价于range(0, 10) 您的range1 不能用于步长与1 不同的范围。 您解释说 range(x) 应该以 0 开头,x 将是“范围的长度”。好的。但是您没有解释为什么 range(x,y) 应该以 x 开头并以 y-1 结尾。如果程序员想要一个 i 从 1 到 3 的 for 循环,他必须显式添加 1。这真的是为了方便吗? for i in range(len(li)): 是一种反模式。应该使用enumerate【参考方案2】:

虽然这里有一些有用的算法解释,但我认为添加一些简单的“现实生活”推理可能有助于解释为什么它会以这种方式工作,我在向年轻的新手介绍该主题时发现这很有用:

对于像“range(1,10)”这样的东西,如果认为这对参数代表“开始和结束”,就会产生混淆。

其实就是开始和“停止”。

现在,如果它“结束”值,那么,是的,您可能期望该数字将作为序列中的最后一个条目。但这不是“结束”。

其他人错误地将该参数称为“count”,因为如果您只使用“range(n)”,那么它当然会迭代“n”次。当您添加 start 参数时,此逻辑就会崩溃。

所以关键是要记住它的名字:“stop”。 这意味着它是当达到时迭代将立即停止的点。不是之后

因此,虽然“开始”确实代表要包含的第一个值,但在达到“停止”值时,它会“中断”,而不是在停止之前继续处理“那个也一样”。

我用来向孩子们解释这一点的一个类比是,具有讽刺意味的是,它比孩子表现得更好!它不会在它应该停止之后 - 它会立即停止而没有完成它正在做的事情。 (他们得到了这个;))

另一个类比 - 当您驾驶汽车时,您没有通过停车/让行/“让路”标志,最终它坐在旁边的某个地方,或后面,你的车。从技术上讲,当您停止时,您仍然没有达到它。它不包括在“您在旅途中传递的东西”中。

我希望其中一些有助于向 Pythonitos/Pythonitas 解释!

【讨论】:

这个解释比较直观。谢谢 @bzip2,Python 不是猪,而是蛇。您所说的“不一致”和“缺陷”并非如此:它们是开发人员做出的设计选择,在整个语言中始终如一地进行,并允许数百万程序员解决他们的任务。如果您不喜欢它,请使用提供包含范围的扩展程序或切换到另一种语言。您的 cmets 对理解 Python 没有任何帮助,反而冒犯了社区。它们还表明您不了解排他间隔的性质,因为如果您在 9 点到 11 点开会,那么您将在 11 点有空。 喜欢停车标志的比喻,很抱歉偷了它:)【参考方案3】:

独占范围确实有一些好处:

一方面,range(0,n) 中的每个项目都是长度为 n 的列表的有效索引。

另外,range(0,n) 的长度为 n,而不是 n+1 的包含范围。

【讨论】:

【参考方案4】:

它与从零开始的索引和len() 结合使用效果很好。例如,如果列表 x 中有 10 个项目,它们的编号为 0-9。 range(len(x)) 给你 0-9。

当然,人们会告诉你使用for item in xfor index, item in enumerate(x) 而不是for i in range(len(x)) 更符合Pythonic。

切片也可以这样工作:foo[1:4]foo 的第 1-3 项(请记住,由于从零开始的索引,第 1 项实际上是第二项)。为了保持一致性,它们应该以相同的方式工作。

我认为它是:“你想要的第一个数字,然后是你想要的第一个数字。”如果你想要 1-10,你不想要的第一个数字是 11,所以它是range(1, 11)

如果它在特定应用程序中变得很麻烦,那么编写一个小辅助函数就很容易了,它将结束索引加 1 并调用 range()

【讨论】:

同意切片。 w = 'abc'; w[:] == w[0:len(w)]; w[:-1] == w[0:len(w)-1]; def full_range(start,stop): return range(start,stop+1) ## helper function【参考方案5】:

它对于分割范围也很有用; range(a,b) 可以拆分为 range(a, x)range(x, b),而在包含范围内,您可以编写 x-1x+1。虽然您很少需要拆分范围,但您确实倾向于经常拆分列表,这是切片列表l[a:b] 包含第 a 个元素但不包含第 b 个元素的原因之一。然后range 具有相同的属性使其很好地保持一致。

【讨论】:

【参考方案6】:

范围的长度是顶部值减去底部值。

这与以下内容非常相似:

for (var i = 1; i < 11; i++) 
    //i goes from 1 to 10 in here

使用 C 风格的语言。

也喜欢 Ruby 的范围:

1...11 #this is a range from 1 to 10

但是,Ruby 认识到很多时候您需要包含终端值,并提供了替代语法:

1..10 #this is also a range from 1 to 10

【讨论】:

【参考方案7】:

考虑代码

for i in range(10):
    print "You'll see this 10 times", i

这个想法是你得到一个长度为y-x 的列表,你可以(如上所示)对其进行迭代。

阅读 the python docs 的范围 - 他们认为 for 循环迭代是主要用例。

【讨论】:

【参考方案8】:

基本上在python中range(n)迭代n次,这是排他性的,这就是为什么它在打印时不给出最后一个值,我们可以创建一个函数,它给出 包含值,这意味着它还将打印范围中提到的最后一个值。

def main():
    for i in inclusive_range(25):
        print(i, sep=" ")


def inclusive_range(*args):
    numargs = len(args)
    if numargs == 0:
        raise TypeError("you need to write at least a value")
    elif numargs == 1:
        stop = args[0]
        start = 0
        step = 1
    elif numargs == 2:
        (start, stop) = args
        step = 1
    elif numargs == 3:
        (start, stop, step) = args
    else:
        raise TypeError("Inclusive range was expected at most 3 arguments,got ".format(numargs))
    i = start
    while i <= stop:
        yield i
        i += step


if __name__ == "__main__":
    main()

【讨论】:

【参考方案9】:

python中的range(n)从0返回到n-1。 range(1,n) 分别从 1 到 n-1。 因此,如果您想省略第一个值并获得最后一个值 (n),您可以使用以下代码非常简单地完成。

for i in range(1, n + 1):
        print(i) #prints from 1 to n

【讨论】:

【参考方案10】:

在很多情况下推理更方便。

基本上,我们可以将范围视为startend 之间的区间。如果start &lt;= end,则它们之间的间隔长度为end - start。如果 len 被实际定义为长度,你会有:

len(range(start, end)) == start - end

但是,我们计算范围中包含的整数,而不是测量区间的长度。为了保持上述属性为真,我们应该包含一个端点并排除另一个。

添加step 参数就像引入一个长度单位。在这种情况下,你会期望

len(range(start, end, step)) == (start - end) / step

长度。要获得计数,只需使用整数除法即可。

【讨论】:

这些对 Python 不一致性的防御非常有趣。如果我想要两个数字之间的间隔,为什么要使用减法而不是间隔来获得差异?对开始和结束位置使用不同的索引约定是不一致的。为什么需要写“5:22”才能获得位置 5 到 21? 这不是 Python 的,它很普遍。在 C、Java、Ruby 中,应有尽有 我的意思是索引很常见,并不是说其他​​语言一定有相同的对象 @Arseny 为 Ruby 辩护,这不是真的。您可以在 Ruby 中构建包含和排除范围:(3..5).include?(5) =&gt; true(3...5).include?(5) =&gt; false。数组切片是明确且包容的:[0,1,2,3,4].slice(0,2) =&gt; [0, 1]。您甚至可以构建开放范围:r = 42..; r.include?(Float::INFINITY) =&gt; true @AndreasGebhard,毫无疑问,在某些情况下这样做很方便。例如,Scala 同时拥有a to ba until b。我的观点是,排除范围的右端是常见的做法,并不是任何不一致的地方。此外,从历史上看,处理器的 &lt; 比较比 &lt;= 比较快

以上是关于为啥范围(开始,结束)不包括结束?的主要内容,如果未能解决你的问题,请参考以下文章

drupal 8 views日期范围过滤器 - '在'运算符之间'不包括结束日期

drupal 8 查看日期范围过滤器-“介于”运算符不包括结束日期

SQL 'overlaps' 仅获取开始和结束之间的日期(不包括开始和结束日期)

来自 True 值范围(开始和结束)的布尔列表,不使用 for 循环

为啥使用循环从数组的开始迭代到结束比迭代开始到结束和结束到开始更快?

熊猫从日期范围列中提取开始和结束日期[重复]