时间:2021-07-01 10:21:17 帮助过:92人阅读
为了表示一个自然数序列 2, 3, …, 12,排除掉中间的那三个点 (...),总共有四种方式可供我们选择:
a) 2 <= i < 13
b) 1 < i <= 12
c) 2 <= i <= 12
d) 1 < i < 13
有没有什么原因使得我们应该选其中的某一种而不是其他的呢?是的,的确有。观察到 a) 和 b) 的优势是不等式的上下界之差恰好是序列的长度。基于此,作为一个中间结论:在 a) 和 b) 两种表达中中,两个序列是邻接的就意味着一个的上界等于另外一个的下界。但这些考量并没有让我们从 a) 和 b) 之中做出选择,所以我们从头开始。
存在一个最小的自然数。排除掉下界——就像 b) 和 d) 中那样——就会使得一个从最小的自然数开始的序列的下界变成非自然数。这样的表达方式很难看,所以对于下界应该更倾向于 <=,就像 a) 和 c) 那样。现在,考虑一下从最小的自然数开始的序列:假如包含上界,当序列缩小成空序列时,就会使得 c) 不那么像自然数集。这样的表达方式也很难看,所以对于上界应该更倾向于 <,就像 a) 和 d) 那样。综上所述, 我们应该倾向于使用 a) 的表达方式。
当处理长度为 N 的序列时,我们期望通过下标来区分它的元素。下一个恼人的问题就是,我们该给它的第一个元素赋予什么下标值?使用 a) 的表达方式,当下标从 1 开始时,下标范围为 1 <= i < N+1;当下标从 0 开始时则是 0 <= i < N,更好看。所以,让我们将序号从 0 开始:一个元素的序号(下标)等于序列中在它前面的元素个数。这个故事的寓意是我们更应该——过了这么多个世纪!——把 0 当作一个自然数。
是 BCPL 作者的杰作,目的是减少编译出的代码里的一个减法指令。@haha王 提到Dijkstra那篇文章。Why numbering should start at zero。不过他并没有翻译全文。这里补充全文翻译。可能会有所错漏。
-----------------------------------
为什么应该从0开始计数为了表示出自然数的子序列,2, 3, ... , 12,不使用省略记号那三个点号,我们可以选择4种约定方式:
是否有什么理由,使选择其中一种约定比其它约定要好呢?是的,确实有理由。可以观察到,a) 和 b)有个优点,上下边界的相减得到的差,正好等于子序列的长度。另外,作为推论,下面观察也成立:在 a),b)中,假如两个子序列相邻的话,其中一个序列的上界,就等于另一个序列的下界。但上面观察,并不能让我们从a), b)两者中选出更好的一个。让我们重新开始分析。
一定存在最小的自然数。假如像b)和d)那样,子序列并不包括下界,那么当子序列从最小的自然数开始算起的时候,会使得下界进入非自然数的区域。这就比较丑陋了。所以对于下界来说,我们更应该采用≤,正如a)或c)那样。现在考虑,假如子序列包括上界,那么当子序列从最小的自然数开始算起,并且序列为空的时候,上界也会进入非自然数的区域。这也是丑陋的。所以,对于上界,我们更应该采用 <, 正如a)或b)那样。因此我们得出结论,约定a)是更好的选择。
讨论:Mesa是由Xerox PARC(施乐帕克研究中心)开发出的编程语言,以上4种表示整数区间的方式,在Mesa中,全部都有专门的记号。使用Mesa的大量经验指出,采用另外三种表示方式,会不断引出拙劣和错误的代码。因此,现今有经验的Mesa程序员强烈建议,不要去使用后面三种特性,尽管它们也可以使用。不管是真是假,我也提出这个实践证据,有些人在结论还没有被实践验证时,会感觉有所不安。(讨论结束)
当处理长度为N的序列时,我们希望通过下标去区分它的元素,下一个值得分析的问题是,最开始的元素应该给予什么样的下标值。我们依然采用a)的约定,当下标从1开始时,下标区间是 1 ≤ i < N + 1;而当从0开始时,可以得到一个更漂亮的区间 0 ≤ i < N。所以,让我们的序数从0开始:一个元素的序数(下标),等于序列中,在它前面的元素个数。这个故事提醒我们,在经过这么多个世纪之后,最好将0当成最自然的数字。
讨论:很多编程语言对于计数的细节并没有足够的重视。在FORTRAN语言中,下标总是从1开始;而在PASCAL语言中,采用了约定c);距今更近的语言SASL, 倒退到FORTRAN的方式:在SAL中,序列也是在正整数上进行操作。(讨论结束)
最近的一次意外事件,促使我作出以上分析。当时,我所在大学的一个数学同事,并非计算机学家,情绪激动地指责一个年轻的计算机学家“迂腐”,因为计算机学家出于习惯而从0开始计数。我的数学同事将一些出于理性考虑后而自觉采用的合理约定,视为挑衅。(就连“...结束”这种约定,也视为挑衅。“...结束”这种约定是有用的:我就知道有个学生,他想当然地认为问题在第一页试卷中结束,而差点没有通过考试。) 我认为Antony Jay陈述得对:“在众人共同组成的宗教中,异教徒必须被驱逐出去,并非因为他们可能是错的,而是因为他们可能是对的。”
这个和数组访问的方式有关,数组是存在内存里的,如何访问一个内存,只需要拿到这个内存的地址就可以了。我们先来看看切片语法的使用吧。它最常见的使用应该是“切出数组的前n位”和“切出数组第i位后的 n位”(前者是后者在i==起始位下的特例)。如果我们不需要使用难看的+1或-1补偿方式,那么代码就会美观许多。
通过使用0-based索引法,Python的半开区间以及缺省匹配区间都很美观,如:a[:n] 和a[i:i+n];前者是a[0:n]的省略写法。
在1-based索引法下,如果你想用a[:n]来表示切出前n个元素的话,你只能选择在切片语法中使用切片起始位和切片长度2个参数,或者闭区间的用法。使用1-based索引法,半开区间切片语法就显得不够美观。同样地,使用闭区间切片语法的话,你只能用a[i:i+n-1]来表示从第i位取n个元素。所以如果使用1-based索引法的话,使用切片长度更合适。你可以写成a[i:n]。事实上,ABC语言就是这样的——它用了一种特殊的用法,写为a@i|n。(参考ABC QUICK REFERENCE)
但是index:length的用法适合其它情况吗?老实说,我不太记得了,但我想我当时的确很喜欢它美观的半开区间语法。特别是两个切片操作位置相邻并且第一个切片操作的终点索引就是第二个切片的起点索引的时候,它的写法实在是太漂亮了。比如,你想以i , j两点来切分一个数组的话,它们将会是a[:i]、a[i:j]、和 a[j:]。
这就是Python 使用0-based索引法的原因。
以上来自Python之父:为什么Python数组下标从0开始
在字符串截取函数中这个问题会得到直观的理解。第一个字,第二个字,第三个字——下标数字应该理解成那个逗号分割位置的编号,而不是位置本身的编号。否则你说1是第一个字的左边还是右边?