人民邮电出版社图灵公司准备重新出版《纯数学教程(纪念版)》。我于 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);
}