duangsuse::Echo
暂时只能写这么多了,写不出来鸟
说起来,用显式栈也真是很长很麻烦,还是直白递归下降好
https://github.com/Netrvin/PageGuard.js
检测developer console打开状况的方法:
+ Function.toString + console.log
+ Regex.toString + console.log
+ Chrome console.profiles
+ window.innerHeight
不让复制的CSS:
检测developer console打开状况的方法:
+ Function.toString + console.log
+ Regex.toString + console.log
+ Chrome console.profiles
+ window.innerHeight
不让复制的CSS:
* {
user-select: none;
-?-user-select: none;
}
input { -?-user-select: auto; }
@media print { * { display: none; } }GitHub
GitHub - Netrvin/PageGuard.js: No copying, printing as well as opening developers tools.
No copying, printing as well as opening developers tools. - Netrvin/PageGuard.js
duangsuse::Echo
所以先梗概一下:其实这个所谓的优化就是显式的递归栈。 我们知道这个二元链解析器是这么工作的: 1+^2*3 每层我们都结合一个中缀[(a+b)什么的]。我们扫描到^的时候,会向前看一个中缀,目的是判断rhs1(这里的"2")该和谁结合 + 如果 (+) 的优先级大,(1+2) + 否则(*)的优先级大,1+ (2*...) 注意上面的, (2 * ...) 这已经暴露了递归子程序需要的参数了:一个是 base (左边的那个表达式)、一个是 (*) 运算符 当它不是作为第一个解析器解析的时候,op_left…
咱来说说为什么少了一个 base。
其实这个递归是非常有模式的,比如说,
在 infixChain1 里我们会提供 base 让它去扫,而不是什么都不加,仅仅遗忘自己刚才扫描的 rhs1
然后 infixChain 也一样
[(1 +), 2] 👈 这就是『新的base』
就是这样,喵。
其实这个递归是非常有模式的,比如说,
在 infixChain1 里我们会提供 base 让它去扫,而不是什么都不加,仅仅遗忘自己刚才扫描的 rhs1
然后 infixChain 也一样
[(1 +), 2] 👈 这就是『新的base』
就是这样,喵。
duangsuse::Echo
现在这个程序是这个样子,咱来看看。
我们举一大堆输入示例,然后一步一步算。
1 — scanInfix() ?: return atomAt0
1 + 1 — op1=(+), [Base(1)], rhs1=1, [Base(+ join 1, 1)]
1 + 2 * 3 — op1=(+), [Base(1)], rhs1=2,
op2=(*), [Tail(1, (+)), Base(2)]
看到了吗?上面 ^
就是第一个『等待结合』的情况,我们又回到了「递归」的原点 — 一次递归扫描的开始
当然,这里我们的 tail 会直接 reduce 一次,显然它必须保证当前栈顶一定是 Base
这是可以保证的,因为我们在每次 push 上 Tail 的时候,都会接着 push 一个 Base,而第一次 push 的是一个 Base。
比如说,假设有 () 优先级大于 (*),0 + 1 * 2 3 显然需要 [Tail(0 +), Tail(1 *), Base(2 3)] — 扫描 op1=() 的时候 scanInfix() 失败,于是它会直接开始规约过程
可是这个栈它是这么构造的:
[Base(0)]
[Tail(0 +), Base(1)] op1=(+), op2=(*)
[Tail(0 +), Tail(1 *), Base(2)] op1=(*), op2=()
[Tail(0 +), Tail(1 *), Base(2)] rhs1=3, op1=(), op2=fail
找不到比较的第二个参数,中缀链扫描完毕,开始回溯组织语法结构。
[Tail(0 +), Tail(1 *), Base(2 3)]
[Tail(0 +), Base(1* (2 3) )]
[Base(0 + (1* (2 ** 3)) )]
至于我要写成连 [Tail(...), Tail(...), Base(...)] 都兼容的模式,用reduce,是没道理的(因为相邻两项都会自然折叠好的……)。
好吧,其实是有道理的,这样会优雅一些(迫真)
1 — scanInfix() ?: return atomAt0
1 + 1 — op1=(+), [Base(1)], rhs1=1, [Base(+ join 1, 1)]
1 + 2 * 3 — op1=(+), [Base(1)], rhs1=2,
op2=(*), [Tail(1, (+)), Base(2)]
看到了吗?上面 ^
就是第一个『等待结合』的情况,我们又回到了「递归」的原点 — 一次递归扫描的开始
当然,这里我们的 tail 会直接 reduce 一次,显然它必须保证当前栈顶一定是 Base
这是可以保证的,因为我们在每次 push 上 Tail 的时候,都会接着 push 一个 Base,而第一次 push 的是一个 Base。
比如说,假设有 () 优先级大于 (*),0 + 1 * 2 3 显然需要 [Tail(0 +), Tail(1 *), Base(2 3)] — 扫描 op1=() 的时候 scanInfix() 失败,于是它会直接开始规约过程
可是这个栈它是这么构造的:
[Base(0)]
[Tail(0 +), Base(1)] op1=(+), op2=(*)
[Tail(0 +), Tail(1 *), Base(2)] op1=(*), op2=()
[Tail(0 +), Tail(1 *), Base(2)] rhs1=3, op1=(), op2=fail
找不到比较的第二个参数,中缀链扫描完毕,开始回溯组织语法结构。
[Tail(0 +), Tail(1 *), Base(2 3)]
[Tail(0 +), Base(1* (2 3) )]
[Base(0 + (1* (2 ** 3)) )]
至于我要写成连 [Tail(...), Tail(...), Base(...)] 都兼容的模式,用reduce,是没道理的(因为相邻两项都会自然折叠好的……)。
好吧,其实是有道理的,这样会优雅一些(迫真)
进一步优化还可以用 JDK 的 Thread local storage,因为每个线程同时只能运行一个函数。
换句话说一个线程对 infixChain() 函数调用有一个栈就够了,用完 clear() 就可以,这是个存储分配方面的优化。
当然,如果你的那个 fun 是 suspend fun 当我没说
为了兼容性这个优化我没有做。
换句话说一个线程对 infixChain() 函数调用有一个栈就够了,用完 clear() 就可以,这是个存储分配方面的优化。
当然,如果你的那个 fun 是 suspend fun 当我没说
为了兼容性这个优化我没有做。
duangsuse::Echo
运行的时候就是这样
然后咱再来测试一下
一确保算法在多个 Tail 同时出现的情况下(看起来)可以正常工作。
0 + 1 * 2 ** 3 这种情况,假定 (**) 也是左结合的:一确保算法在多个 Tail 同时出现的情况下(看起来)可以正常工作。
噢是我定义新前缀的时候搞错了,本来是 ascending order 的 🐱
我们注意到,本来是 0+ 2*
(0+) 的提前和 2 弄混了…… 都是在扫描 rest 的时候搞的
看来还是思路错了…… Tail 是不可能被铺平的,除非你真不用伪递归(就是基于
(0+) 的提前和 2 弄混了…… 都是在扫描 rest 的时候搞的
看来还是思路错了…… Tail 是不可能被铺平的,除非你真不用伪递归(就是基于
Recursion<T> 的递归)
duangsuse::Echo
我们注意到,本来是 0+ 2* (0+) 的提前和 2 弄混了…… 都是在扫描 rest 的时候搞的 看来还是思路错了…… Tail 是不可能被铺平的,除非你真不用伪递归(就是基于 Recursion<T> 的递归)
突然觉悟了,为什么 Tail 里面必须是 Atom 啊?也可以是递归的 Tail 啊!
不过这样就等于是白建一个栈了……
不过这样就等于是白建一个栈了……
我想到一种很自然的解决方法:
把栈表达为 [Base(0), Tail(+), Base(2), Tail(*), Base(3!4!1)] 的形式
然后我们回溯的时候修改一下策略,让它结合 lhs=pop() 就可以了
把栈表达为 [Base(0), Tail(+), Base(2), Tail(*), Base(3!4!1)] 的形式
然后我们回溯的时候修改一下策略,让它结合 lhs=pop() 就可以了