为啥范围(开始,结束)不包括结束?
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 x
或for 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-1
或 x+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】:在很多情况下推理更方便。
基本上,我们可以将范围视为start
和end
之间的区间。如果start <= 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) => true
但(3...5).include?(5) => false
。数组切片是明确且包容的:[0,1,2,3,4].slice(0,2) => [0, 1]
。您甚至可以构建开放范围:r = 42..; r.include?(Float::INFINITY) => true
@AndreasGebhard,毫无疑问,在某些情况下这样做很方便。例如,Scala 同时拥有a to b
和a until b
。我的观点是,排除范围的右端是常见的做法,并不是任何不一致的地方。此外,从历史上看,处理器的 <
比较比 <=
比较快以上是关于为啥范围(开始,结束)不包括结束?的主要内容,如果未能解决你的问题,请参考以下文章
drupal 8 views日期范围过滤器 - '在'运算符之间'不包括结束日期
drupal 8 查看日期范围过滤器-“介于”运算符不包括结束日期
SQL 'overlaps' 仅获取开始和结束之间的日期(不包括开始和结束日期)
来自 True 值范围(开始和结束)的布尔列表,不使用 for 循环