改進《純數學教程(紀念版)》中的根式

黃志斌發表於2019-02-26

人民郵電出版社圖靈公司準備重新出版《純數學教程(紀念版)》。我於 2013 年 10 月寫了一篇文章“《純數學教程(紀念版)》中的根式”,說:書中的根式裡面都有不必要的括號。

現在我們來去掉這些不必要的括號。這本書是使用 LaTeX 排版的,第 31 頁第 32 題的第 1 個根式的 TeX 程式碼如下:

\[
\sqrt {\left( {\sqrt[3]{5} - \sqrt[3]{4}} \right)} =
\frac{1}{3}\left( {\sqrt[3]{2} + \sqrt[3]{20} -
\sqrt[3]{{25}}}\right),
\]

去掉不必要的括號後變為:

\[
\sqrt { {\sqrt[3]{5} - \sqrt[3]{4}} } =
\frac{1}{3}\left( {\sqrt[3]{2} + \sqrt[3]{20} -
\sqrt[3]{{25}}}\right),
\]

第 2 個根式:

\[
\sqrt[\mbox{\raisebox{0.8ex}{\mbox{{\mbox{${}^3$}}}}}]{\left(
{\sqrt[3]{2} - 1} \right)}=
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{\left(
{\frac{1}{9}} \right)}-
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{\left(
{\frac{2}{9}} \right)}+
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{\left(
{\frac{4}{9}} \right)},
\]

去掉不必要的括號後變為:

\[
\sqrt[\mbox{\raisebox{0.8ex}{\mbox{{\mbox{${}^3$}}}}}]{
{\sqrt[3]{2} - 1} }=
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{
{\frac{1}{9}} }-
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{
{\frac{2}{9}} }+
\sqrt[\mbox{\raisebox{1.5ex}{\mbox{{\mbox{${}^3$}}}}}]{
{\frac{4}{9}} },
\]

我寫了一個 C# 程式來完成這個任務,執行結果如下:

$ mcs sqrttransformer.cs && ./sqrttransformer.exe 
   5   3 ../ch00.tex
 273  41 ../ch01.tex
 109  50 ../ch02.tex
  58  34 ../ch03.tex
  52  10 ../ch04.tex
  53  36 ../ch05.tex
 216 156 ../ch06.tex
  70  61 ../ch07.tex
 101  62 ../ch08.tex
 112  75 ../ch09.tex
  59  46 ../ch10.tex
   7   7 ../ch99.tex
1115 581 Total
$

可以看出,全書共有 1115 個根式(包含巢狀的根式),其中有 581 個根式需要去掉不必要的括號。第 6 章需要去掉不必要的括號的根式最多,有 156 個。

源程式如下:

 1 using System;
 2 using System.IO;
 3 
 4 static class SqrtTransformer
 5 {
 6   static readonly string S = "\\sqrt", L = "\\left", R = "\\right";
 7   static readonly string[] Ls = {"(","[","\\{",L+"(",L+"[",L+"\\{"};
 8   static readonly string[] Rs = {")","]","\\}",R+")",R+"]",R+"\\}"};
 9   static int n1 = 0, n2 = 0;
10 
11   static string DeleteBracket(string s)
12   {
13     if (s.Length < 5 || s[0] != '{' || s[s.Length - 1] != '}') return s;
14     var i0 = 1; while (char.IsWhiteSpace(s, i0)) i0++;
15     for (var k = 0; k < Ls.Length; k++) {
16       if (!s.Substring(i0).StartsWith(Ls[k])) continue;
17       var i1 = s.IndexOf(Rs[k], i0 += Ls[k].Length);
18       if (i1 < 0) throw new Exception("Right bracket not found");
19       var i2 = i1 + Rs[k].Length; while (char.IsWhiteSpace(s, i2)) i2++;
20       if (i2 + 1 != s.Length) return s; // right bracket isn't most right
21       return Transform("{" + s.Substring(i0, i1 - i0) + "}");
22     }
23     return s;
24   }
25 
26   static (int Left, int Right) GetBoundary(string s, int i)
27   {
28     for (i += S.Length; char.IsWhiteSpace(s, i); ) i++;
29     if (s[i] == '[') { while (s[i] != ']') i++; i++; }
30     while (char.IsWhiteSpace(s, i)) i++; int i0;
31     if (s[i0 = i] != '{') return (i, i); //TODO: s[i] == '\\'
32     for (var n=1; n>0;) if (s[++i]=='{') n++; else if (s[i]=='}') n--;
33     return (i0, i); // s[i0] == '{' && s[i] == '}'
34   }
35 
36   static string Transform(string s)
37   {
38     var t = new System.Text.StringBuilder();
39     for (int j, i = 0; i < s.Length; i++) {
40       if ((j = s.IndexOf(S, i)) < 0) { t.Append(s.Substring(i)); break; }
41       var (b, d) = GetBoundary(s, j);  t.Append(s.Substring(i, b - i));
42       string s2, s1 = s.Substring(b, (i = d) - b + 1);
43       t.Append(s2 = DeleteBracket(s1)); n1++; if (s1 != s2) n2++;
44     }
45     return t.ToString();
46   }
47 
48   static void Main()
49   {
50     int i1 = 0, i2 = 0;
51     foreach (var file in Directory.GetFiles("..", "ch??.tex")) {
52       File.WriteAllText(file, Transform(File.ReadAllText(file)));
53       Console.WriteLine("{0,4} {1,3} {2}", n1 - i1, n2 - i2, file);
54       i1 = n1; i2 = n2;
55     }
56     Console.WriteLine("{0,4} {1,3} Total", n1, n2);
57   }
58 }

簡要說明:

  • 第 51 行的 foreach 迴圈遍歷全書各章(ch00.tex, ch01.tex, ...)。
  • 第 52 行的 File.ReadAllText 方法把某一章全部讀入記憶體。
  • 然後呼叫 Transform 方法進行轉換(去掉不必要的括號)。
  • 接著呼叫 File.WriteAllText 方法把這一章寫回 ch??.tex 檔案。
  • 第 36 至 46 行的 Transform 方法執行轉換。主要是查詢 "\sqrt",然後呼叫 GetBoundary 方法找出其後的以 '{' 和 '}' 表示的左右邊界,再去掉其中的不必要的括號。
  • 第 26 至 34 行的 GetBoundary 方法尋找 "\sqrt" 的左右邊界。
  • 第 29 行跳過 "[...]"(對應於開平方以外的情況)。
  • 第 31 行對應被開方數是單個符號的情況。其中有可能是 \Delta 這樣用多個字元表示的單個符號,但是對我們的目的沒有影響,為簡單起見,直接定界為 \ 就行了。
  • 第 32 行定界 "{...}" 的情況,其中允許有巢狀的 "{}"。
  • 第 11 至 24 行的 DeleteBracket 方法刪除不必要的括號。
  • 第 16 行判斷根式中是不是以左括號開頭。
  • 第 17 行尋找匹配的右括號。
  • 第 18 行處理找不到匹配的右括號的情況。
  • 第 20 行處理右括號後面不是緊接著右邊界的情況,此時這對括號是必要的,不能刪除。
  • 第 21 行遞迴呼叫 Transform 方法以刪除巢狀的根式中的不必要的括號。

如果需要正確處理被開方數是 \Delta 這樣的情況,可以把第 31 行替換為:

if (s[i0 = i] != '{') {
  if (s[i] != '\\') return (i, i);
  for (i0 = i++; char.IsLetter(s, i); ) i++;
  return (i0, i - 1);
}

相關文章