總數間隔格式問題

lua-users home
wiki

本文討論基於 1 的索引與封閉間隔分割和基於 0 的索引與右開間隔分割的綜合問題。根據我的看法,其他解決方案並不一致。

[原始作者註:儘管本文最先表達我對此主題的觀點,它作為一個 wiki 頁面,旨在進一步闡述(同時進行潤色,我不是英文母語人士!)。然而,我已經思考並討論此問題一段時間,特別是因為我參與了培訓;因此,在開火前,您也可以考慮想一想。編輯後移除此註解。 --DeniSpir (denis.spir at free.fr),2008 年 10 月 8 日。]

戰爭的起源?

整個爭論點的根源在於程式設計的歷史。C 等一些語言的實作細節是,陣列項目透過指標 + 位址偏移量來參照。因此,偏移量存在於實作層級,這導致語言設計者必須選擇將此架構轉移到語言本身,使程式設計師必須適應它,或讓編譯器或解釋器從所有索引減 1,以便(通常為人類)程式設計師可以使用序數。C 沒有選擇這樣做,因此 C/C++/Java/Python/Ruby 整個系列語言家族也沒這樣做。由於 C 非常成功,希望他們的「寶貝」被廣泛使用的語言設計者必須考慮這個事實上的標準,以及除了他們也是 C 程式設計師這項事實。現在,宣稱一個功能是「自然的」,因為它是一個標準,這僅僅是邏輯錯誤。

C 中使用零索引是一個選擇,許多(可能大部分)程式設計師認同這個選擇,而且你不能只假設他們繼續偏好這個選擇,只是因為熟悉度或慣例(除非你把你的結論當作前提)。你的最後一句話和「寶貝」等詞匯,不公平地把所有反對你觀點的人一視同仁。 �MarceloCantos?

活動

人皆有之

陣列、串列、序列和表格都是有序的集合。人類使用序數--第一、第二、第三(或 #1、#2、#3)--來指出集合中的一個元素(哪一個?)。相反地,他們使用基數--1、2、3--來計算元素的數量(多少?)。不幸的是,程式設計大多以英語為主,而且該語言會將這兩種數字類型都稱為「數字」(與德文的 Nummer/Zahl 或法文的 num�ro/nombre 相比)。儘管如此,所有語言對於這兩者之間都會做出重大的區別。人類生活的各個領域中,除了程式設計的一個子領域之外,序數都會用於指出事物。尋找第 0 年、書架上的第 0 本書,或貝克街 0 號 ;-)。此外,序數似乎最先出現,這也是為什麼「零」這個基數在人類歷史中如此年輕的原因。語言見證了這個事實:人們會說「沒人」、「從未」和「沒什麼」,而不是使用「零」這個詞。所有這些的目的是說,在人類層面上,對程式設計師友善的選擇是使用序數,而不是偏移量,來參考有序集合中的項目。

人類也會將 0 用在一小時的第一分鐘。另外,想想基於 1 的計數強加於我們的愚蠢。新千年始於 2001/1/1。 a[(i - 1)%n + 1] 。一天的第一小時是 12,而最後一小時是 11(看起來他們心思縝密地讓一點從午夜後一小時開始,但當午夜本身到來時卻亂了套)。十人份的清單總是有一組兩位數字;這徹底搞混了孩子們學習從 1 數到 10 以及閱讀到 10,當他們必須學習零時,更是让他们混亂不已。一趟行程的第一公里從 0 公里開始。 �MarceloCantos?

您關於「沒人」、「從未」和「沒什麼」這些詞彙的論點是 無關論證。這些都是基數項,而非序數項。將這些詞彙用來描述基數為零的集合或許有點奇怪,但这與序數是否應從 0 開始的問題關聯不大。 �MarceloCantos?

類似地,閉區間恰巧更自然或直觀。「從第一個到第三個」在任何情況下都是一個包含式的含義。因此,使用半開區間首先需要心智運作,最後才會自動化。這既不表示這種語法較沒效率或較不合理,僅是對於非入門者而言較不人性化。有些程式設計師似乎對於這類導致新手上路的錯誤的深奧功能感到自豪 ;-)。

再次強調,使用「自豪」和「深奧」等情緒性字眼是一種 人身攻擊的修辭技巧,試圖透過非理性論述的方式來影響讀者。 �MarceloCantos?

有些程式設計師發現半開範圍比封閉範圍是一種更正統的系統,且能產生比封閉範圍更可靠的程式碼,而且這種可靠性凌駕於錯誤千年以來,為了讓外行人感到「直觀」的習慣。 �MarceloCantos?

這裡有必要區分程序和人類語意層次。在程序層次上,這兩個方案同樣適用,而且語意都相符。在人類層次上,0 起始點/半開範圍方案需要從程序層進行一種「轉碼」。因此,如果某人同意程式碼(例如數學表達式)會先由人類而非機器讀取,那麼這個方案會是不佳的選擇——只有在有理由證明替代方案不可能時,才應選擇這個方案。請注意,與 C 一樣古老的 Pascal 採取的是相反的方式——這並非巧合,因為它的設計適合用於教育。

程式設計師也是

在日常工作中,一旦習慣了其中一種慣例,兩種方案都證明可以使用。當程式設計師討論這個問題時,他們往往會堅持個人的舒適習慣,也就是每天使用所建立的心智習慣。由於大多數使用的語言遵循 C 的慣例,因此有許多論點支持它。現在,仔細觀察後,這些話恰好錯了,因為它們並非合乎邏輯的理由:它們是理性的表達背後的意見

這兩種方案都可以使用,但是半開範圍顯著的更簡單、更合理而且不易發生 off-by-one 錯誤。 �MarceloCantos?

例如,關於以 [n,n) 最佳表達的 0 長度範圍的論點或多或少是沒有意義的;表達空序列的唯一明智方式是 [][5,5)[5,4] 只是在語義上荒謬。現在,它可能發生在執行期間,序列切片是空的——這是另一個層次:程式設計師不會在設計期間處理它。

首先,一個小的瑕疵:論點或多或少有價值,但是說它沒有意義毫無道理。 �MarceloCantos?

使用 [] 這種方式來表示空的範圍很尷尬。電腦程式通常會使用具備兩個端點的資料結構:struct range { int start, end; };。兩個此類範圍的相交守則非常簡單:range intersect(range a, range b) { range r = { max(a.start, b.start); min(a.end, b.end); if (r.end < r.start) r.end = r.start; return r; },基數也很簡單:int count(range r) { return r.end - r.start; },空的函數也很簡單:bool empty(range r) { return r.start == r.end; }。只要您需要為空的範圍使用不同的表示法,所有事項都會突然變得複雜,並耗盡更多 RAM 和 CPU。您還會發現自己非得在程式碼中廣泛地灑上 + 1- 1,以修正各種因為一而十的問題(例如,現在計算需要 r.end - r.start + 1,以及明確測試空狀態)。 �MarceloCantos?

封閉範圍對於連續量來說極為困擾。您要如何表示對應於一天單位的時間戳記範圍?半開放範圍很容易表達這一點:[2010-01-01, 2010-01-02)。封閉範圍需要這樣表達:[2010-01-01, 2010-01-01 23:59:59],其中至少存在兩個問題:1) 它勢必假設量子;2) 這是表達一天這個簡單概念的不自然且複雜的方式。將半開放範圍分割成連續且不相交的子範圍也很容易:[e, pi) 可以分割成 [e, 3) 和 [3, pi)。就一般情況而言,封閉範圍無法做到這一點,即便使用量子,也不太可能很明顯地知道成對的 [2010-01-01, 2010-01-01 11:59:59] 和 [2010-01-01 12:00:00, 2010-01-01 23:59:59] 連續(量子是一秒嗎?)。儘管許多這類問題對於陣列索引等離散應用而言更為容易解決,但半開放範圍仍然較容易操作。一個簡單的範例,顯而易見的是 [a, b) 和 [b, c) 是連續的,然而 [a, b] 和 [c, d] 需要進一步檢查程式碼。此外,將半開放範圍以 n 個方式分割,例如 [a, b), [b, c), ..., [y, z) 表示成陣列,[a, b, c, ..., z] 較為容易;反之,將封閉範圍以 n 個方式分割,則必須小心記住您儲存的是每個分割的開始還是結束。 �MarceloCantos?

此外,請考慮「半開區間」的「長度」等於end - start,而不管該區間是否為連續或離散,而「閉區間」的「長度」公式對於連續和離散區間來說是不同的。作為一個實際且很常見的範例,半開日期區間中的天數計算方式是一樣的,而不管終點是日期還是時間戳記。這也是我比較偏好date1 <= d AND d < date2而非 SQL 的BETWEEN運算子的原因;我不用思考有問題的變數是日期還是時間戳記,或擔心維護人員是否會將其中一個類型切換到另一個類型。 �MarceloCantos?

[5,5)是一個退化的情況,而此類案例通常很有用。究竟是什麼讓它荒謬? �MarceloCantos?

和平之光

在 21 世紀,這樣的時間和精力損失是否仍會發生?至少有兩種方法可以解決此問題。

明確語法

正如某些註解,BASIC 允許明確表達陣列索引配置。例如,array(0,10)會定義一個從 0 開始的索引範圍。從類似的觀點來看,可以使用任一數學通用語法明確使用半開區間: [a,b) [a,b[ 。此選項有額外的優點,可以消除容易出錯的格式,因為所使用的配置已明確寫入。這些對人類讀者來說是很好的,但問題仍然是正確解讀未習慣的慣例所寫的表達式。以下提案解決了這個問題。

編輯器自訂化層級

作為程式設計師,我們都很熟悉良好的編輯器自訂化功能,例如縮排選項,讓我們可以選擇是使用標籤還是空格,或縮排寬度應為何者。請注意,這可以用於讀取/載入或編輯/儲存原始碼檔案。無論作者選項為何,我們都可以使用自己偏好的慣例來讀取和編輯程式碼。無論我們的選項為何,另一位開發人員都將能夠使用自己的選擇來讀取和編輯相同的程式碼。現在,檔案可以依據任何標準規範儲存,無關緊要。這是一種透過編輯器自訂化層級執行的前景/背景區分。太棒了!

現在,何不將此原則延伸到*任何*語言功能?例如,有人可能會希望擺脫賦值用的「=」(這在語意上是錯誤的),將它替換為例如「:」,並只將「=」用於邏輯等式。載入檔案時,編輯器應使用這些偏好顯示程式碼,而不管儲存時所使用的標準為何。

索引和切片也是一樣:C 程式設計師會設定 0 為起始點的半開切片,並以這種方式取得顯示的程式碼,而不管儲存時的背景標準為何。習慣使用 Delphi 的程式設計師會選擇相反的配置,無論如何。

[你對此有何看法?我現在主要使用 Python 程式設計,而大多數針對這類語言的 IDE 也都是用 Python/wxPython 編寫的。因此,當我有時間時,我的目標是將此功能實作到一個編輯器中...]

「恐龍出沒。」編輯器如何將 a[len - 1 - i] 轉換為從 1 開始的編排方式?它會直接將其索引包覆起來,如下所示:a[(len - 1 - i) + 1],還是嘗試分析內容,尋找「- 1」以移除,或「+/- <constant>」用於變更,如下所示:a[len - i]?如果程式碼未完成該怎麼辦:a[len - ] 或者乾脆想相反:a[~i & 0xff] /* len == 256 */?而如何處理 C++,它允許你覆寫 operator []?另外請考慮,程式設計人員經常在討論程式碼時共用螢幕,這可能會造成極其嚴重的混淆。 MarceloCantos

另請參閱


近期變更 · 偏好設定
編輯 · 歷程記錄
最後編輯於 2020 年 6 月 2 日下午 3:50(格林威治標準時間)(diff)