算盘计数

描述分析

临时信息:需要更合适的例子。 该用例采用 Rust 分组机制无法表示的匹配嵌套结构, 实在是过于特殊,因此不适作为例子使用。

注意:此节假设读者已经了解 下推累积 以及 标记树撕咬机

macro_rules! abacus {
    ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
        abacus!(($($moves)*) -> ($($count)*))
    };
    ((- $($moves:tt)*) -> ($($count:tt)*)) => {
        abacus!(($($moves)*) -> (- $($count)*))
    };
    ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
        abacus!(($($moves)*) -> ($($count)*))
    };
    ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
        abacus!(($($moves)*) -> (+ $($count)*))
    };

    // Check if the final result is zero.
    (() -> ()) => { true };
    (() -> ($($count:tt)+)) => { false };
}

fn main() {
    let equals_zero = abacus!((++-+-+++--++---++----+) -> ());
    assert_eq!(equals_zero, true);
}

这个例子所用的技巧用在如下情况: 记录的计数会发生变化,且初始值为零或在零附近,且必须支持如下操作:

  • 增加一;
  • 减少一;
  • 与 0 (或任何其它固定的有限值)相比较;

数值 n 将由一组共 n 个相同的特定标记来表示。 对数值的修改操作将采用 下推累积 模式由递归调用完成。 假设所采用的特定标记是 x ,则上述操作可实现为:

  • 增加一:匹配($($count:tt)*)并替换为(x $($count)*)
  • 减少一:匹配(x $($count:tt)*)并替换为($($count)*)
  • 与0相比较:匹配()
  • 与1相比较:匹配(x)
  • 与2相比较:匹配(x x)
  • (依此类推...)

作用于计数值的操作将所选的标记来回摆动,如同算盘摆动算子。1

1

在这句极度单薄的辩解下,隐藏着选用此名称的 真实 理由: 避免造出又一个名含“标记”的术语。今天就该跟你认识的作者谈谈避免 语义饱和 吧! 公平来讲,本来也可以称它为 “一元计数(unary counting)”

在想表示负数的情况下,值 -n 可被表示成 n 个相同的其它标记。 在上例中,值 +n 被表示成 n+ 标记,而值 -m 被表示成 m- 标记。

有负数的情况下操作起来稍微复杂一些, 增减操作在当前数值为负时实际上互换了角色。 给定 +- 分别作为正数与负数标记,相应操作的实现将变成:

  • 增加一:
    • 匹配 () 并替换为 (+)
    • 匹配 (- $($count:tt)*) 并替换为 ($($count)*)
    • 匹配 ($($count:tt)+) 并替换为 (+ $($count)+)
  • 减少一:
    • 匹配 () 并替换为 (-)
    • 匹配 (+ $($count:tt)*) 并替换为 ($($count)*)
    • 匹配 ($($count:tt)+) 并替换为 (- $($count)+)
  • 与 0 相比较:匹配 ()
  • 与 +1 相比较:匹配 (+)
  • 与 -1 相比较:匹配 (-)
  • 与 +2 相比较:匹配 (++)
  • 与 -2 相比较:匹配 (--)
  • (依此类推...)

注意在顶部的示例中,某些规则被合并到一起了 (举例来说,对 ()($($count:tt)+) 的增加操作被合并为对 ($($count:tt)*) 的增加操作)。

如果想要提取出所计数目的实际值,可再使用普通的 计数宏 。对上例来说,终结规则可换为:

macro_rules! abacus {
    // ...

    // 下列规则将计数替换成实际值的表达式
    (() -> ()) => {0};
    (() -> (- $($count:tt)*)) => {
        - ( count_tts!($( $count_tts:tt )*) )
    };
    (() -> (+ $($count:tt)*)) => {
        count_tts!($( $count_tts:tt )*)
    };
}

// 计数一章任选一个宏
macro_rules! count_tts {
    // ...
}

仅限此例: 严格来说,想要达到此例的效果,没必要做的这么复杂。 如果你不需要在宏中匹配所计的值,可直接采用重复来更加高效地实现:

macro_rules! abacus {
    (-) => {-1};
    (+) => {1};
    ($( $moves:tt )*) => {
        0 $(+ abacus!($moves))*
    }
}

算盘游戏

译者注:这章原作者的表述实在过于啰嗦,但是这个例子的确很有意思。 基于这个例子框架,我给出如下浅显而完整的样例代码(可编辑运行):

macro_rules! abacus {
    ((- $($moves:tt)*) -> (+ $($count:tt)*)) => {
        {
            println!("{} [-]{} | [+]{}", "-+1", stringify!($($moves)*), stringify!($($count)*));
            abacus!(($($moves)*) -> ($($count)*))
        }
    };
    ((- $($moves:tt)*) -> ($($count:tt)*)) => {
        {
            println!("{} [-]{} | - {}", "- 2", stringify!($($moves)*), stringify!($($count)*));
            abacus!(($($moves)*) -> (- $($count)*))
        }
    };
    ((+ $($moves:tt)*) -> (- $($count:tt)*)) => {
        {
            println!("{} [+]{} | [-]{}", "+-3", stringify!($($moves)*), stringify!($($count)*));
            abacus!(($($moves)*) -> ($($count)*))
        }
    };
    ((+ $($moves:tt)*) -> ($($count:tt)*)) => {
        {
            println!("{} [+]{} | + {}", "+ 4", stringify!($($moves)*), stringify!($($count)*));
            abacus!(($($moves)*) -> (+ $($count)*))
        }
    };

    (() -> ()) => {0};
    (() -> (- $($count:tt)*)) => {{-1 + abacus!(() -> ($($count)*)) }};
    (() -> (+ $($count:tt)*)) => {{1 + abacus!(() -> ($($count)*)) }};
}

fn main() {
    println!("算盘游戏:左边与右边异号时抵消;非异号时,把左边的符号转移到右边;左边无符号时,游戏结束,计算右边得分");
    println!("图示注解:左右符号消耗情况,分支编号,[消失的符号] 左边情况 | [消失的符号] 右边情况\n");

    println!("计数结果:{}\n", abacus!((++-+-+) -> (--+-+-)));
    println!("计数结果:{}\n", abacus!((++-+-+) -> (++-+-+)));
    println!("计数结果:{}\n", abacus!((---+) -> ()));
    println!("计数结果:{}\n", abacus!((++-+-+) -> ()));
    println!("计数结果:{}\n", abacus!((++-+-+++--++---++----+) -> ())); // 这是作者给的例子 :)
}

打印结果:

算盘游戏:左边与右边异号时抵消;非异号时,把左边的符号转移到右边;左边无符号时,游戏结束,计算右边得分
图示注解:左右符号消耗情况,分支编号,[消失的符号] 左边情况 | [消失的符号] 右边情况

+-3 [+]+ - + - + | [-]- + - + -
+-3 [+]- + - + | [-]+ - + -
-+1 [-]+ - + | [+]- + -
+-3 [+]- + | [-]+ -
-+1 [-]+ | [+]-
+-3 [+] | [-]
计数结果:0

+ 4 [+]+ - + - + | + + + - + - +
+ 4 [+]- + - + | + + + + - + - +
-+1 [-]+ - + | [+]+ + + - + - +
+ 4 [+]- + | + + + + - + - +
-+1 [-]+ | [+]+ + + - + - +
+ 4 [+] | + + + + - + - +
计数结果:4

- 2 [-]- - + | - 
- 2 [-]- + | - -
- 2 [-]+ | - - -
+-3 [+] | [-]- -
计数结果:-2

+ 4 [+]+ - + - + | + 
+ 4 [+]- + - + | + +
-+1 [-]+ - + | [+]+
+ 4 [+]- + | + +
-+1 [-]+ | [+]+
+ 4 [+] | + +
计数结果:2

+ 4 [+]+ - + - + + + - - + + - - - + + - - - - + | + 
+ 4 [+]- + - + + + - - + + - - - + + - - - - + | + +
-+1 [-]+ - + + + - - + + - - - + + - - - - + | [+]+
+ 4 [+]- + + + - - + + - - - + + - - - - + | + +
-+1 [-]+ + + - - + + - - - + + - - - - + | [+]+
+ 4 [+]+ + - - + + - - - + + - - - - + | + +
+ 4 [+]+ - - + + - - - + + - - - - + | + + +
+ 4 [+]- - + + - - - + + - - - - + | + + + +
-+1 [-]- + + - - - + + - - - - + | [+]+ + +
-+1 [-]+ + - - - + + - - - - + | [+]+ +
+ 4 [+]+ - - - + + - - - - + | + + +
+ 4 [+]- - - + + - - - - + | + + + +
-+1 [-]- - + + - - - - + | [+]+ + +
-+1 [-]- + + - - - - + | [+]+ +
-+1 [-]+ + - - - - + | [+]+
+ 4 [+]+ - - - - + | + +
+ 4 [+]- - - - + | + + +
-+1 [-]- - - + | [+]+ +
-+1 [-]- - + | [+]+
-+1 [-]- + | [+]
- 2 [-]+ | - 
+-3 [+] | [-]
计数结果:0