<?xml version='1.0' encoding='UTF-8'?>
<rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0"><channel><title>mBlog titles</title><link>https://www.jiamo.name</link><description>add https://www.jiamo.name/rss to subscribe the blog </description><docs>http://www.rssboard.org/rss-specification</docs><generator>python-feedgen</generator><lastBuildDate>Tue, 21 Apr 2026 10:34:58 +0000</lastBuildDate><item><title>Y-combinator 的推导</title><link>https://www.jiamo.name/blog/blog/1</link><description>非原创文章。

算是某些文章的翻译。加上一点自己的理解。

```scheme
(define (f n)
  (if (= n 0)
      1
      (* n (f (- n 1)))))

(f 5)
```

不用到自己定义自己，把 f 当作参数

```scheme
(define (p0 self n)
  (if (= n 0)
      1
      (* n (self self (- n 1)))))

(p0 p0 5)
```

柯里化， 两个参数变一个

```scheme
(define (p1 self)
  (lambda (n)
    (if (= n 0)
        1
        (* n ((self self) (- n 1))))))

((p1 p1) 5)
```

提升 (self self) ， racket 不是 lazy 的，总是看到啥执行啥。 

如下定义会 无限执行下去。 尝试移除 (self self) 

```scheme
(define (p2-wrong self)
  (let ((f (self self)))   
    (lambda (n)
     (if (= n 0)
          1
          (* n (f (- n 1)))))))

((p2-wrong p2-wrong) 5)
```

lambda make it lazy to eval

let ((f (self self)) 是即时计算 

let ((f (lambda (x) ((self self) x)) 延迟计算, 先定义了一个函数。

(self self) 和 (lambda (x) ((self self) x) 在使用的时候是等价的。

再次尝试去掉 (self self)

```scheme
(define (p2 self)
    (let ((f (lambda (x) ((self self) x))))
      (lambda (n)
        (if (= n 0)
            1
            (* n (f (- n 1)))))))

((p2 p2) 5)
```

let expression can be converted into an equivalent

lambda expression using this equation:

(let ((x &lt;expr1&gt;)) &lt;expr2&gt;) ==&gt; ((lambda (x) &lt;expr2&gt;) &lt;expr1&gt;) 

就是说把 x 变量当作参数处理。

```scheme
(define (p3 self)
  ((lambda (f)
     (lambda (n)
       (if (= n 0)
           1
           (* n (f (- n 1))))))
   (lambda (x) ((self self) x)) ))

((p3 p3) 5)
```

;; 抽出一个函数处理，简化推导

```scheme
(define g
    (lambda (f)
      (lambda (n)
        (if (= n 0)
            1
            (* n (f (- n 1)))))))

(define (p4 self)
  (g (lambda (x) ((self self) x)) ))

((p4 p4) 5)
```

继续简化 (p4 p4) 这样可以直接传入变量 n

(define p5 (p4 p4))

(p5 5)

展开 p5 的定义, 定义一个本地变量

```scheme
(define p6
    (let ((p4-local
           (lambda (self)
               (g  (lambda (x) ((self self) x))))))
      (p4-local p4-local)))

(p6 5)
```

make the name short，p4-local is just a name

```scheme
(define p7
    (let ((f (lambda (self)
               (g (lambda (x) ((self self) x))))))
      (f f)))
```

再次使用 (let ((x &lt;expr1&gt;)) &lt;expr2&gt;) ==&gt; ((lambda (x) &lt;expr2&gt;) &lt;expr1&gt;)

```scheme
(define p8
  ((lambda (f) (f f))
   (lambda (self) (g  (lambda (x) ((self self) x)) ))))

(p8 5)
```

make the name short， self is just a name

```scheme
(define p9
  ((lambda (f) (f f))
   (lambda (h) (g (lambda (x) ((h h) x)) ))))

(p9 5)
```

g 在这里是全局变量。可被化简为参数。 而不是特定的 g， 变 p9 为 Y

```scheme
(define Y
    (lambda (g)
      ((lambda (f) (f f))
       (lambda (h) (g (lambda (x) ((h h) x)))) )))

( (Y g) 5)
```

把 (lambda (h) (g (lambda () ((h h) y)))) 应用在 f 上

```scheme
(define Y1
    (lambda (g)
      ((lambda (h) (g (lambda (x) ((h h) x))))
       (lambda (h) (g (lambda (x) ((h h) x)))) )))
```

再进一步可以把之前 即时求值的替换

简化公式，但 racket 不能使用。

(lambda (y) ((h h) y)) is just mean (h h)

haskell / lazy-racket can do it

```scheme
(define Y2
    (lambda (g)
      ((lambda (h) (g (h h)))
       (lambda (h) (g (h h))))))
```

* * *

Y 参数化的作用使得求解更加通用

```scheme
(define g1
    (lambda (f)
      (lambda (n)
        (if (= n 0)
            1
            (* n (f (- n 1)))))))

(define f1
  (lambda (n)
        (if (= n 0)
            1
            (* n (f1 (- n 1))))))
```

g1 和 f1 可以应用；

((g f1) 5) ;

不同的函数

```scheme
(define g2
    (lambda (f)
      (lambda (n)
        (if (= n 0)
            1
            (*  2 (f (- n 1)))))))

(define (f2 n)
  (if (= n 0)
      1
      (* 2 (f2 (- n 1)))))   ;; wrong ((g2 f2) 5)

((g2 f2) 5)
```

不同的版本的 g 确实可以运行/但只能应用特定版本的 f， 而且 g f 大量重复

((g1 f2) 5)

((g2 f1) 5) 都是错误的结论。

* * *

参考文章：

1\. &lt;https://mvanier.livejournal.com/2897.html&gt;

2\. &lt;https://www.cs.toronto.edu/~david/courses/csc324_w15/extra/ycomb.html&gt;</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/1</guid><pubDate>Tue, 23 Feb 2021 02:34:12 +0000</pubDate></item><item><title>栈虚拟机，寄存器虚拟机，抽象机</title><link>https://www.jiamo.name/blog/blog/2</link><description>整理收集了 Graham Hutton ([http://www.cs.nott.ac.uk/~pszgmh/](&lt;http://www.cs.nott.ac.uk/~pszgmh&gt;))

几个简单浅显的虚拟机。

stack machine &lt;https://github.com/pa-ba/calc-comp&gt;

```
data Expr = Val Int | Add Expr Expr  
-- Interpeter
eval            ::  Expr -&gt; Int
eval (Val n)    =   n
eval (Add x y)  =   eval x + eval y

data Code = HALT | PUSH Int Code | ADD Code
            deriving Show
--- Compiler
comp        ::  Expr -&gt; Code
comp e      =   comp' e HALT

comp'        ::  Expr -&gt; Code -&gt; Code
comp' (Val n)   c =   PUSH n c
comp' (Add x y) c =   comp' x (comp' y (ADD c))

--- Stack virtual machine
type Stack = [Int] 

exec                  ::  Code -&gt; Stack -&gt; Stack
exec HALT s           =   s
exec (PUSH n c) s     =   exec c (n:s)
exec (ADD c) (m:n:s)  =   exec c ((n+m) : s)

exec (comp (Add (Add (Val 2) (Val 3)) (Val 4)))
```

* * *

reg machine &lt;https://github.com/pa-ba/reg-machine&gt;

这里可以理解 Mem 为寄存器池。通过提供存取函数模拟。

```
data Expr = Val Int | Add Expr Expr 
            deriving Show
            
data Code = LOAD Int Code | STORE Reg Code | ADD Reg Code | HALT
            deriving Show
             
compile :: Expr -&gt; Code
compile e = comp e first HALT
 
comp :: Expr -&gt; Reg -&gt; Code -&gt; Code
comp (Val n)   r c = LOAD n c
comp (Add x y) r c = comp x r (STORE r (comp y (next r) (ADD r c)))

type Mem = Reg -&gt; Int
type Reg = Int
type Conf = (Acc,Mem)
type Acc = Int

empty :: Mem
empty = \n -&gt; 0

set :: Reg -&gt; Int -&gt; Mem -&gt; Mem
set r n m = \r' -&gt; if r == r' then n else get r' m

get :: Reg -&gt; Mem -&gt; Int
get r m = m r

first :: Reg
first = 0

next :: Reg -&gt; Reg
next r = r+1

exec :: Code -&gt; Conf -&gt; Conf
exec (LOAD n c)  (a,m) = exec c (n,m)
exec (STORE r c) (a,m) = exec c (a, set r a m)
exec (ADD r c)   (a,m) = exec c (get r m + a, m)
exec HALT        (a,m) = (a,m)

fst $ exec (compile nine) (0,empty)
```

* * *

&lt;http://www.cs.nott.ac.uk/~pszgmh/123.pdf&gt; 中抽象机实在太不直观，也无法感知这样的写法到底优雅在哪里。概念不是很直白。但是感觉很有技巧。

文章本来是 eval' ，但被我改成 comp_eval。

整个执行过程，给人的感觉是一边 compile 一边 execute。

```
data Expr = Val Int | Add Expr Expr 
data Cont = HALT | EVAL Expr Cont | ADD Int Cont 

eval :: Expr -&gt; Int
eval e = comp_eval e HALT

comp_eval :: Expr -&gt; Cont -&gt; Int
comp_eval (Val n) c = exec c n
comp_eval (Add x y) c = comp_eval x (EVAL y c) 
-- 先 compile x , 再把 y 放到后面执行
-- EVAL 隐含 add 的信息

exec :: Cont -&gt; Int -&gt; Int
exec HALT n = n 
exec (ADD n c) m = exec c (n+m)
exec (EVAL y c) n = comp_eval y (ADD n c) 
```

其中执行过程，并不比普通的解释器过程来的简单。

之所以说没看懂，是因为其推导过程非常复杂，而且不直观。

总感觉左右两边的 Add 和 Eval 互相隐含，但直白的想出来挺难的。

文中原话

&gt; _eval_ ′ and _exec_ are mutually recursive, which corresponds to the machine having two modes of operation, depending on whether it is currently being**driven by the structure of the expression or the control stack**

两种 model 毕竟不是一种。 这个上面的 栈虚拟机和寄存器虚拟机概念反而更一致一些。都是执行翻译完的指令。

* * *

关于 mutually recursive , 这个链接

&lt;http://okmij.org/ftp/Computation/fixed-point-combinators.html#Poly-variadic&gt;

里面的用了 Y-combinator

```
fix_poly:: [[a]-&gt;a] -&gt; [a]
fix_poly fl = fix (\self -&gt; map ($ self) fl)
  where fix f = f (fix f)
    
test1 = (map iseven [0..5], map isodd [0..5])
  where
  [iseven, isodd] = fix_poly [fe,fo]
  fe [e,o] n = n == 0 || o (n-1)
  fo [e,o] n = n /= 0 &amp;&amp; e (n-1)
```

通俗的解释如下。

```python
even :: Int -&gt; Bool
even 0 = True
even n = odd (n - 1)

odd :: Int -&gt; Bool
odd 0 = False
odd n = even (n - 1)
```

even 和 odd 函数类型是一样的。 但抽象机的 comp_eval 和 exec 的类型不一样。

受到此 even 和 odd 的函数类型是一样的启发。

我觉得上面的抽象机也是可以变得类型一样。最终尝试得到了下面的东西。

和论文里面本质是一样的（只不过函数类型变的一样了）

```
comp_eval3 :: Expr -&gt; Cont -&gt; Int 
comp_eval3 (Val n) c   = exec3 (Val n) c
comp_eval3 (Add x y) c  = comp_eval3 x (EVAL y c) 

exec3 :: Expr -&gt; Cont -&gt; Int 
exec3 (Val f) HALT  = f 
exec3 (Val f) (ADD n c) = exec3 (Val (f+n)) c 
exec3 (Val f) (EVAL y c)  = comp_eval3 y (ADD f c)

eval3 :: Expr -&gt; Int
eval3 e = comp_eval3 e HALT
```

为什么是 eval3 其实中间还有一个丑陋的推导版本。（最后发现第三个参数就是第一个参数的一种形变）

```
eval2 :: Expr -&gt; Int
eval2 e = comp_eval2 e HALT 0

comp_eval2 :: Expr -&gt; Cont -&gt; Int -&gt; Int
comp_eval2 (Val n) c m  = exec2 (Val (n+m)) c n
comp_eval2 (Add x y) c m = comp_eval2 x (EVAL y c) m 
-- 先 compile x , 再把 y 放到后面执行
-- EVAL 隐含 add 的信息

exec2 :: Expr -&gt; Cont -&gt; Int -&gt; Int
exec2 (Val f) HALT n = n 
exec2 (Val f) (ADD n c) m = exec2 (Val (f+m+n)) c (n+m)
exec2 (Val f) (EVAL y c) n = comp_eval2 y (ADD n c) (f)
```</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/2</guid><pubDate>Tue, 23 Feb 2021 03:16:33 +0000</pubDate></item><item><title>快速排序的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/3</link><description>我喜欢考察面试者对递归和迭代的理解。

一般会先出一道如何使用递归求解，再出一道使用迭代求解上题。

本来准备用快速排序作为考题。想了想自己作为面试者，肯定是不会的。

就没有为难面试者。

但是作为算法分析题，这两种技巧还是需要掌握。

我自己就花时间调研了一下：

递归版：

```python
def partition(A, lo, hi):
    tmp_p = int(lo + (hi - lo) / 2) 
    tmp_value = A[tmp_p]
    A[lo], A[tmp_p] = A[tmp_p], A[lo]
    p = lo
    for i in range(lo + 1, hi + 1):
        if A[i] &lt; tmp_value:
            p += 1
            A[i], A[p] = A[p], A[i]
    A[p], A[lo] = A[lo], A[p]
    return p

def quick_sort(lst, start, end):
    if start &lt; end:
        split = partition(lst, start, end)
        quick_sort(lst, start, split - 1)
        quick_sort(lst, split + 1, end)
```

迭代版 (同一 partition)

```python
def quicksort(A):
    lo = 0
    hi = len(A) - 1
    range_queue = Queue()
    range_queue.put([lo, hi])
    while not range_queue.empty():
        lo, hi = range_queue.get()
        if lo &lt; hi:
            p = partition(A, lo, hi)
            range_queue.put([lo, p - 1])
            range_queue.put([p + 1, hi])
```

因为不是所有递归算法都可以转为迭代的，或者很难转化

上面快速排序的迭代，我是看到 Leslie Lamport 的视频，才知道。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/3</guid><pubDate>Tue, 23 Feb 2021 03:23:49 +0000</pubDate></item><item><title>垃圾回收简介</title><link>https://www.jiamo.name/blog/blog/4</link><description>纯属阅读总结。

《垃圾回收算法手册：自动内存管理的艺术》英文版还是靠谱一点。

* * *

**1\. 标准引用计数**

![](/files/gc1.png)

![](/files/gc2.png)

* * *

**2\. 延迟引用计数**

![](/files/gc3.png)

先不释放 childrean 在重新分配使用的时候（**复用** ）发现有 children 才顺便释放。

* * *

**3\. 标记清扫**

First, live memory is marked by traversing the object graph starting at the roots. 

Next, all unmarked memory is reclaimed in the sweep phase. 

The algorithm starts by marking the roots. Marking an object includes finding its children and marking them. 

By marking the roots, all reachable objects will be marked. 

The sweeping phase traverses the heap and all unmarked objects found are reclaimed.

简单翻译一下： 从根开始标记活着的对象，标记完成之后遍历堆回收没有被标记的。

整个算法过程是不断构建一个标记对象的树。从根开始，扫描其子对象，再扫描子对象的子对象。

所以某一时刻，**自己和所有子对象****已标记完的对象（黑色）** ，自己被扫描 ，但自己的**子对象没有被标记完（灰色）** ，**自己没有被标记（白色）** 。清扫阶段就是清扫掉还是白色的对象。

想要正确的构建这课标记树，需要在构建过程中维持**三色不变式** ：

保证已扫描完的对象（黑色）永远不指向未被扫描的对象（白色）。为什么（要保证树构建的正确性，维持这个不变式）

如果不 stop-the-world 并发执行 GC， 那么中途有正常程序的赋值，黑色对象会指向一个白色对象（**即产生一个白色的子对象，可以理解指向就是引用，见下图** ）

![](/files/gc4.png)

如何保证，摘选几种 barrier (对象赋值和读取的拦截代码)

![](/files/gc5.png)

意思是： 如果之前是白色的对象，赋值之后，左值变成灰色。

  
![](/files/gc6.png)

意思是： 赋值之后，白色右值变成灰色。

所以这两种都是保证，黑色不指向白色。必然是把打破的一方改变颜色。

* * *

**4\. 标记整理**

![](/files/gc7.png)

Two pointers are used to point out the first free region, and the last reachable region. Objects are moved from the end of the heap to the first free slot. When the two pointers meet, all memory is compacted. Forward references, which are stored in the first cell of moved objects, are then used to update references from other objects. The algorithm does not require any extra space (except for the mark bit), but the objects are re-ordered. This is not a problem in most systems, but some systems depend on the ordering of objects.

简单翻译一下： 活着对象从后往前填充空闲 slot 。两个游标相等时算法结束。

（需要注意的是，被移动的对象，可能被指，那么指向关系需要更改到正确的地址）

* * *

**5\. 复制算法**

![](/files/gc8.png)

上图是一种形象的描述。 构建标记图还是需要的（虚拟的构建）。只不过这个构建过程是把灰色的对象移动到 to-space 而且需要构建对象指向的正确关系。扫描完成之后，from-space 可以整体清除。

暂时放弃理解 stop-the-world 。

具体构建算法：

![](/files/gc9.png)

A GC cycle starts by copying the roots into to-space. When an object is copied, the new address is stored in the from-space copy. Since the from-space copy will never be referred to again, the forward address can over- write data in the object. Thus, no extra field is needed.

Objects in to-space are grey or black. To separate grey objects from black ones, a pointer named scan is used. **Objects to the left of scan have been scanned and are thus black.** The object that scan points to is being scanned. While an object is being scanned, its children are examined**. If a child reference refers into from-space and the object has not been copied, it is copied into to-space a** nd the child reference is updated to refer to the to- space copy. If the child has been copied, the child reference is updated to refer to the to-space copy using the forward address stored in from-space.**When all roots have been copied and all copied objects have been scanned, the collector is finished.（这是一个迭代的过程。根对象一个接着一个的处理。）**

The algorithm is a stop-and-copy algorithm, because it stops the mutator to perform garbage collection. Thus, it may interrupt the mutator for long periods of time which makes it unsuitable for use in most real-time systems.

简单翻译一下： scan 指向正在扫描的对象，其子对象在 from 的就移动到 to 。要注意移动的时候是往空闲区放。而且只有被 scan 对象所有子对象移动完，才能标记为黑。（其实就是虚拟的构建一个标记树）

想要不**stop-the-world** 。 意味着复制时也可以正常执行程序，**要维持三色不变式。** 同样需要 barrier。

![](/files/gc10.png)

意思是： 赋值时，如果右对象还在 from-space. 需要把它移动到 to-space 。forward 一个字段表示我指向的地方可以代替我。读的拦截就是，并发过程中 forward 永远指向对的地方。（参考写的代码）</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/4</guid><pubDate>Tue, 23 Feb 2021 03:29:45 +0000</pubDate></item><item><title>Postgresql Sync Elasticsearch</title><link>https://www.jiamo.name/blog/blog/5</link><description>一句话解释这个功能，使用 postgresql logic decoding 感知数据库的变化，同步到其他数据源。

如果是同步到 postgresql 数据库， 直接使用 logic replication 。

如果是 elasitcsearch 这类异构数据源。就需要解析复制内容。

我这里使用 [wal2json](&lt;https://github.com/eulerto/wal2json&gt;)[ ](&lt;https://github.com/eulerto/wal2json&gt;)这个插件。

**1\. 怎么使用的。**

AWS RDS 的参数 必须要把 rds.logical_replication 改为 1

创建 replication slot

```sql
SELECT * FROM pg_create_logical_replication_slot('wal2json_rds', 'wal2json');
```

查看 replication slot

```sql
SELECT *  FROM pg_replication_slots;
```

使用 [psycopg2](&lt;https://github.com/psycopg/psycopg2&gt;) 仿照 [python-mysql-replication](&lt;https://github.com/noplay/python-mysql-replication&gt;) 这个库 开发了 psql 版本 [python-psql-replication](&lt;https://github.com/jiamo/python-psql-replication&gt;)

具体能用的人，自然可以看出其实没有啥技术含量。当然也可以做的更好。

**2\. 有什么坑。**

2.1 

没有更新事务的个情况下 wal 一直在涨。 但是 slot 却不推进它的 confirmed_flush_lsn

参考刚刚使用这种方法的时候。我提的一个 [stackoverflow](&lt;https://stackoverflow.com/questions/52589058/aws-rds-postgresql-transaction-logs-keep-going-when-there-is-no-data-change&gt;) 问题： 

解决办法： 我暂时使用评论里面的提到的 aws 的方法， 定期搞点事务出来

（当然是不影响正常数据，处理方式我自己觉得羞愧）

2.2 

我遇见过即时 alter table test_table replica identity full 之后 ，

wal2json 解析出来的的 event["row"]["after_values"] 依旧没有包含所有的列。

这个线上最好不要 alter table test_table replica identity full 量大产生日志太多。

identity full 有个好处是。入 elasticsearch 的时候。可以不用 update 直接 index 了。

(如果之前出现错误没有index进去)

2.3 

同步脚本如果已经追不上了。怎么调试。

```sql
select pg_current_wal_flush_lsn();

select pg_current_wal_insert_lsn();

select pg_current_wal_lsn();

selet  *  from  pg_replication_slots;

select
      slot_name,
      pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(),restart_lsn))
          as replicationSlotLag, 
      active 
from pg_replication_slots ;
```

观察各类 lsn 的值。

在不关心已有 wal 的数据情况， 和不想删除 slot 的情况下

最好是调试 psycopy2 中 [start_replication](&lt;http://initd.org/psycopg/docs/extras.html#psycopg2.extras.ReplicationCursor.start_replication&gt;) 中的参数，看能否推进。

最粗暴的方式就是 直接删掉 slot 重来。

* * *

本文只是记录自己怎么干这件事的。

在没有深入理解 postgresql 的机制的情况下（羞愧），

这个方法暂时没有带来太多的处理负担。

需要鞭策自己真的深入理解。否则出了问题真的是抓瞎。

* * *

不得不反问自己。

在没有 在没有深入理解 postgresql 的情况下。怎么敢这个干？

答 ： 因为在 mysql 那边也干过。

那是深入理解 mysql 了吗？

答 ： 其实也有没有。（羞愧）

* * *

上面的思路形成：&lt;https://github.com/jiamo/pg_to_es&gt;

但 [https://github.com/toluaina/pgsync/tree/master/pgsync ](&lt;https://github.com/toluaina/pgsync/tree/master/pgsync &gt;) 做的更好</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/5</guid><pubDate>Tue, 23 Feb 2021 03:38:59 +0000</pubDate></item><item><title>数组 JOIN</title><link>https://www.jiamo.name/blog/blog/6</link><description>Q:

PSQL 表含有一列 id 数组，如何查询 id 对应名字的数组。（至于为什么当初不是 json 数组不在此问题的讨论范围）

A:

```sql
CREATE TABLE items
(
    id   SERIAL PRIMARY KEY,
    name text
);

insert into items(name)
values ('name1'),
       ('name2'),
       ('name3'),
       ('name4');

CREATE TABLE test_items
(
    id    SERIAL PRIMARY KEY,
    items int[]
);

insert into test_items(items)
values (array [1, 2]),
       (array [3, 4]);

select
    distinct test_items.id,
    test_items.items,
    array_agg(items.name) over (partition by test_items.id)
from test_items
    left join items on items.id = any (test_items.items)
```

重点关注 partition any distinct 的用法。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/6</guid><pubDate>Tue, 23 Feb 2021 07:26:35 +0000</pubDate></item><item><title>需要人为干预的多分类排行榜的设计</title><link>https://www.jiamo.name/blog/blog/7</link><description>排行榜设计需求：

  1. 体现自然榜
  2. 总榜和分类榜
  3. 可以人为干预，干预了总榜需要影响分榜。

比如极端情况下总榜前 8 名都是人为干预的。在分类榜里面，某一个专辑可能在自然榜排第一，

也要排在总榜第 8 名后面。

（因为客户端看到的总榜第 8 名， 在分类榜里面看到其他专辑在它之上是不合理的）

* * *

我做了如下设计：

top_type 分自然榜和干预榜。

```sql
create table daily_top_board

(
   id bigserial PRIMARY KEY,
   top_date timestamp with time zone,
   category_id uuid,
   album_id uuid,
   top_type smallint,
   total_rank smallint,    
   genre_rank smallint

);

create unique index daily_top_board_index
   on daily_top_board (top_date, album_id);
```

* * *

由于客户端呈现的分类榜需要干预榜的全部信息

需要查出干预榜之后，动态排完，再过滤掉非此分类的专辑。

省略得到排过序的自然榜和干预榜查询 sql 。

其中动态重排的主要过程如下：

```python
def merge_helper(alist, blist, key=None):
    # 其中 alist 是自然榜 ，blist 是干预榜，都已排序
    #（同事 xd 参与讨论过）

    c, aindex, bindex, alen, blen, cindex = \
        [], 0, 0, len(alist), len(blist), 0

    while aindex &lt; alen and bindex &lt; blen:
        aitem_data = alist[aindex]
        aitem = key(aitem_data) if key else aitem_data
        bitem_data = blist[bindex]
        bitem = key(bitem_data) if key else bitem_data
        if bitem &lt;= aitem:
            c.append(bitem_data)
            bindex += 1
        else:
            if (cindex + 1) &lt; bitem:
                c.append(aitem_data)
                aindex += 1
            else:
                c.append(bitem_data)
                bindex += 1
        cindex += 1

    if aindex == alen:
        c.extend(blist[bindex:])

    if bindex == blen:
        c.extend(alist[aindex:])

    return c
```

* * *

附带看看自然榜的产生，与之前的 model 对应。

# 表的设计假定了 所有专辑都会有分类

# 不用个巨大的榜，总榜拿前 200 个。每个分类必须有前 100 个。

# 如果是一个巨大的榜，动态插入更新运营榜，会带来巨大的数据写入。

```sql
with album_score as (

    select
        id as album_id,
        album_category.category_id,
        row_number() over ( order by 一些计算公式 desc)
              as total_rank,
        row_number() over 
         ( partition by album_category.category_id order by 一些计算公式 desc )
              as genre_rank

    from album
    left join album_duration on album.id = album_duration.album_id
    left join album_category on album_category.album_id = album.id 
    -- 省略一下过滤条件

), rank_data as (
    select
        album_score.category_id,
        album_score.album_id,
        album_score.genre_rank as genre_rank,
        album_score.total_rank as total_rank
    from album_score where
        (album_score.genre_rank &lt; 101 or
         album_score.total_rank &lt; 201
        ) and
         album_score.category_id is not null

) insert into top_board (
    top_date, category_id, album_id, total_rank, genre_rank, top_type)
  select
    $3,
    rank_data.category_id,
    rank_data.album_id,
    rank_data.total_rank,
    rank_data.genre_rank,
    1
    from rank_data
    on conflict (top_date, album_id)  
    do nothing

    ----- 用这种方式去掉重复专辑。但这里如果干预榜出现了这个专辑 
    ----- 这种设计就丢失了这个专辑原来的排名了，设计就需要取舍。
    ----- （要不然查询中去除重复，还涉及干预榜，很复杂）
```

总结：

  1. 由于每个分类榜都需要前 100 个专辑，但某些冷门的分类，在总榜可能排名很低。如果构建一个巨大的总榜，浪费资源。
  2. 干预榜。干预榜明确的需求就是指定排名。
  3. 其中隐含的内部需求（如：运营人员怎么调控，怎么设置排名，操作之后对客户端的影响）设计都需要考虑周全。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/7</guid><pubDate>Tue, 23 Feb 2021 07:38:52 +0000</pubDate></item><item><title>改变世界</title><link>https://www.jiamo.name/blog/blog/8</link><description>A: 为什么总是有人要拿改变世界说事？

B: 那拿什么？

A: 我先提的问。

B: 为了表达自己的意义和存在，谁不想改变世界，青史留名呢？

A: 可是好多人说改变世界，我都发现特别的傻，特别的胡扯？

B: 所以只有真的已经改变了世界，这个说法才显的不那么傻。

A: 那什么才叫改变了世界？世界一直在变啊，你怎么就说这个变化就是你触发的呢？

B: 这就是为什么牛顿和莱布尼兹争优先权，爱因斯坦还担心希尔伯特的原因。

A: 所以说大家公认? 或者历史承认？如果是大家公认，多少人承认才是？如果是历史，人都已经死了。

B: 去中心化，不能你说是什么就是什么。

A: 我想改变世界。

B: 光想还是没有用的，需要行动。需要一以贯之的行动。

A: 我可以假象自己已经改变了世界，然后到处吹嘘吗？

B: 当然不可以，高人看见了，会戳破你的。

A: 那就尽量不在高人面前吹嘘。

B: 给他们一点点甜头是可以让他们闭嘴的。再不行给点苦头也是可以的。

A: 除了去改变世界，人还可以做点什么别的？

B: ......

A: 只能去改变世界？

B: 我记得有个老人说过，“认识你自己”

A: 和自己相关的怎么弄都可以，什么三省吾身，内心平静，放浪形骸等等，没有其他人参与的事件说到底可以自己定义一切，然后重新定义。我还是想让其他人知道，我改变了世界。我听说好多个团体都在改变世界，是不是可以加入它们呢？

B: 那最后还是团体改变了，没有人会记得你的。

A: 那只能当团体的老大了。

B: 当老大和改变世界还真是可以相互替换，这一点你成功了。

A: 为什么总是有两个和两个以上的大哥？

B: 人无法忍受只有一个活着的大哥，否则大哥就是上帝了。

A: 可以不去选一个大哥吗？

B: 否定不产生实体或意志，我们必须选择，我们必将选择，当个大哥，或者当个小弟。

A: “王侯将相宁有种乎？” 。为什么有的是大哥，有的是小弟。

B: 有的人时而是大哥又时而是小弟，取决别人说的，还是自己说的呢？你觉得他们的区别是什么呢？

A: 我知道只有一个珠穆拉玛峰，它是最高的。

B: 宇宙是无穷的。没有实际的点。

A: 那么说，大哥和小弟其实没有啥区别，因为找不到最大的大哥？

B: 你是唯心主义者。

A: 说了这么多，以后我改变了世界会告诉你的。

B: ...... 那你可能要给我点甜头

A: 真是那样，我会选择给你点苦头。

B: ...... 

​</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/8</guid><pubDate>Thu, 25 Feb 2021 07:57:22 +0000</pubDate></item><item><title>生存，生活，生命，自己，他人，人类</title><link>https://www.jiamo.name/blog/blog/10</link><description>A: 生存是什么？

B: 一日三餐，不会冻死，不会渴死，粒粒皆辛苦。

A: 生活是什么？

B: 一日三餐，有点小酒，没有闲事，日日好时节。

A: 生命是什么？

B: 一日三餐，参透俗世，通晓万物，遨游于天地。

A: 你现在处于哪个状态呢？

B: 大概是生存状态。

* * *

A: 什么是自己？

B: 生命中的自己。

A: 什么是他人？

B: 生活中的他人。

A: 什么是人类？

B: 生存中的人类。

A: 你活在什么世界中？

B: 大概是人类的世界。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/10</guid><pubDate>Thu, 25 Feb 2021 09:02:51 +0000</pubDate></item><item><title>事实到底是什么</title><link>https://www.jiamo.name/blog/blog/11</link><description>​A: 是否有检验对错的标准？

B: 对就是对，错就是错。要标准，那标准的对错该怎么办？

A: 不同的人对同一件事情看法不一样，有的人认为是对，有的认为是错。

B: 这就需要坐下来慢慢谈。

A: 要一个人说服一个人？那被说服的人就一定错了吗？

B: 物以类聚，人以群分，所以三观相同的人容易走到一起。

A: 但是我发现很多组织里面，三观不同也在一起工作，学习，生活的。那又怎么解释？

B: 说明组织还在进化。

A: 所以我们不能判定对错？

B: 我们需要跳出来看这个问题。比如一件事我们不去看对错，而去看事实。事实是什么，比对错更容易辨别。

A: 事实不也是一种意见吗？描述事实就开始涉及对错判定了。

B: 所以多人都在描述，看你愿意相信谁的描述。

A: 那你的意思是有的人的智商就是比我高？我应该相信他的。

B: 但他有可能会欺骗你。你就像朝三暮四的猴子一样，看不清楚。

A: 我更像是朝秦暮楚的人，谁给我好处，我就信谁的。

B: 个人利益和个人快乐之间的博弈。本是认为对，厉害关系却说错。

A: 利益和快乐还会冲突？

B: 冲突带来精神世界的碰撞，才能在虚妄和无助之中苟活。

A: 所以一直都很快乐的人，都是在欺骗自己或者被别人欺骗。因为他们本不应该能看清快乐的事实。只是他们自认为的快乐？

B: 但他们有自洽的逻辑。

A: 有些人总是认为他们在追求真理。这里的真理和我们之前谈的对错又有没有区别？

B: 那些追求真理的人，同样也觉得自己是快乐的。和你说的快乐的人又有什么不同？

A: 吃饱了饭，才会去想精神世界吧。

B: 没吃饱的人就没有精神世界了？

A: 所以人世间就没有所谓的真假，只有自己给自己套上的枷锁？

B: 又走入极端。为什么有的人能想出非对称加密出来，你却不可以？

A: 这里涉及不到真假，只有发现。

B: 天上又不会掉下馅饼。

A: 所以事实并不重要，重要的是发现事物运作的规律？

B: 你以为你改变了世界的进程，但此刻就是世界本身。

A: 我要放下对事实的判断，对规律的发现，对世界的认识。

B: 那你将要如何活命。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/11</guid><pubDate>Thu, 25 Feb 2021 09:56:50 +0000</pubDate></item><item><title>论规律</title><link>https://www.jiamo.name/blog/blog/12</link><description>A: 世界有运转的规律吗？

B: 此刻存在就是规律的体现。若不然，你为什么此时此刻有这个问题？而我还能回答你。

A: 可是这个规律应用在不同的人身上，起到的效果不一样。

B: 这就像一个函数一样，不同输入 x 会有不同输出 y 。

A: 但是有时候相同的人，一样的规律，为什么结果却不同。 

B: 希腊先哲不是说过 “人不能两次踏进同一条河流”。 同样的人怎么会存在呢？

A: 那岂不是说人总是在变化，此前的我和现在的我没有干系？ 可是也有老话，“江山易改，本性难移”，那么这个本性又是什么呢？

B: DNA 是不可以变了。或许规律的输入参数是 (人， 环境) 因为环境不同了。相同的人自然得到的结果不同。

A: 可是环境，人都是无穷无尽的。这个规律不可能匹配所有人和环境。那么一定有一种归纳方法，约简人和环境。也许是一种分类法。但依旧需要遍历所有人，不真正看到这个人，怎么能就说这个人是这个分类呢？

B: 这种分类的方法，也应该嵌入在这个规律之中。但是单一的规律，很难证明它的存在性，很难构造出来。

A: 是不是把规律假想成单一的大规律会走进死胡同，或许有很多规律，f₁ f₂ f₃ 只是不同时间点上，被应用了不同的规律？

B: 有 f₃ 肯定会有 f₄ 如此往复，规律无穷无尽，那我们谈的是规律，也就不是规律了。怎么才能知道在哪个环境下，在哪个人上用哪个规律呢？这不成了规律之上的规律了？

A: 那是不是，不应该把 `人` 应用到规律上 (f x)， 应该是规律应用到人上 (x f)。就是世界规律还是只有一个，但是不知道怎么回事，有很多不同的人，看起来是在检验这个规律的正确性。所以结果不一样，就不奇怪了。

B: 我们还是不应该相信这个规律的存在。假设是为了检验规律的正确性，那么谁去检验呢？参与检验的人岂不是没有自我意识了，就像被写的测试用例一样。那样我们认为的自我意识，也只是测试用例的参数修改而已。

A: 那么不用管规律了？昏头昏脑的过日子也是可以的？

B: 虽然不知道有没有规律，或者知道有规律，应用在自己身上是什么结果也不知道？但我们得尽力去和规律互动。因为毕竟还是有不同的输出。

A: 是不是可以把规律想象成一个迷宫，有很多出口，有很多机关，我浑浑噩噩的呆着不动或者如脚踩西瓜一样，倒不如看到什么不断尝试，主动选择，柳暗花明一样。

B: 所以人人生而平等，但也要自我奋斗。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/12</guid><pubDate>Thu, 25 Feb 2021 10:11:08 +0000</pubDate></item><item><title>事实和非事实</title><link>https://www.jiamo.name/blog/blog/13</link><description>```python
​postulate
  ¬¬-elim : ∀ {A} → ¬ ¬ A → A

lem : ∀ {A} → A ⊎ ¬ A
lem = ¬¬-elim ( λ f → f ( inj₂ ( λ x → f ( inj₁ x))))

postulate
  em : ∀ {A : Set} → A ⊎ ¬ A

dne : ∀ {A : Set} → ¬ ¬ A → A
dne ¬x = case-⊎ (λ x → x) (λ x → (exFalso (¬x x))) em
```

A: 这上面是表达什么意思

B: 是说排中律和双重否定律等价。但无法构造出其中一个。

A: 用人的语言说呢？

B: 我不是不爱国就说我爱国，等价于， 我除了爱国就是不爱国了。但是却无法构造或证明我爱国和不爱国。

A: 我挥舞着国旗，高唱着国歌，不能说明我爱国？你冷嘲热讽，甚至蒙面侮辱国旗，不能说明你不爱国？

B: 表演需要有观众，成为演员就放弃了表现和他真实的一致性。所以只能说他表演的很爱国，很不爱国，但精神灵魂的拷问，无法说出所以然来。

A: 可是每个人都要接触其他人，必然是演员了啊。

B: 所以说才会谈论演技的好坏，有的人演不出自己本性，就会让人觉得浮夸。有的人本色出演，但总让人觉得有点过，觉得入戏太深。

A: 为了表现反抗的反抗，和为了表现的认同的认同就差不多一样，不用认真对待？

B: 你真是蠢，别人打你一下表现反抗，和给你 100 元，表示认同能一样吗？既然都是表演，当然是为了最大利益。

A: 可是短期的利益和长期利益总是不容易分清，打我一下可能是为了让我记住教训，给我 100 元，可能是为了腐蚀我。

B: 所以身为演员的我们，需要识别其他演员真实的表演性，并能清楚的认知自己的表演性。比如这两个演员，司马迁和刘彻你记住了谁？你更愿意记住谁呢？

A: 我不知道，可是我们都是被迫参演。我到底是不是演员呢？难不成有人的时候是演员，没有人的时候就是自己了？

B: 就像 "死火" 一样，没有燃烧起来叫死，燃烧起来叫火，燃烧完的刹那，就是死火。所以死火是活的，还是死的呢？

A: 在有人和无人的边界上做自己?

B: 所以 ‘我思故我在’ 你现在懂了吗？

(至于我对话的表演性的自指是不言而喻的)</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/13</guid><pubDate>Thu, 25 Feb 2021 10:17:16 +0000</pubDate></item><item><title>诗一首</title><link>https://www.jiamo.name/blog/blog/14</link><description>悠扬婉转的歌声，惊醒了熟睡的黄牛。

冬天下雪的日子，很少看见捕鸟的少年。

看不见美丽的东西，以为全是丑陋！

看不见光明的前途，内心只有黑暗。

要从那白色中，发现黑色，将欢乐化作痛苦。

是善让我活着，

是假让我执着。

我以为一切都是泡沫！

有人说那就是美。

多少个日夜，思索着去哪里。

多少次岔路口，吹者口哨。

黑夜的霓虹，称自己是彩虹。

梦不到神，那是惩罚！

看到了鬼，那是惩罚！

太阳去的地方，多么好的世界。

晚霞与青山，一个个死的物体。

我要属于我自己的时间和空间。

那里没有人或物。

我要我一个人的世界，那里有血与泪。

食物和水像欲望一样，

人把它变成了习惯。

让我奔跑，却不告诉我方向。

秋天会有风，会有雁。

我不会爬树，我会歌唱。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/14</guid><pubDate>Thu, 25 Feb 2021 10:21:05 +0000</pubDate></item><item><title>外部推手和不光滑的世界</title><link>https://www.jiamo.name/blog/blog/15</link><description>​倾向于假定没有自由意志，

因为一切推导链的最后要么用无意义的偶然解释，

要么就要创造出一个外部推手。

我们不能假定这个外部推手是什么，

因为我们在内部，看不到外部，能看到外部，我们就不是内部了

你说人不给汽车加油和充电，汽车可以跑起来吗。

类比一下，没有推手给人类安放一个自由意志，人还能思考吗。

但总感觉这个类比有点奇怪，

惯性定律告诉我们，没有外力，球在光滑的地面要么静止，要么匀速运动。

没有外界推手，人也是一层不变的。

假定没有外部推手，世界又不是光滑的，所以人最终会停止。

人最终又没有停止。 （这里的人可以理解人的境界，经济实力，文明程度等等。）

假定有外部推手，世界是光滑的，人要么停止（被阻止），要么飞上天（被拉）。

但至今为止人也没有飞上天（意象）。

得出的结论是，有外部推手，而且世界也不是光滑的。

这难道是常识？

* * *

如何面对外部推手和不光滑的世界？

不要去正面想它，像它不存在一样的生活。

停止和飞天，就是一种宿命。

是外界推手和不光滑的世界共同作用的结论，

不是单个个体能左右的。

外部推手和我们太过遥远。所以个人只能处理不光滑的世界。

光滑的地面，两个人相对推手，向相反的方向发展。

如果是一个人拉一个人，就可以同向前进。

但拉人的人，有可能被被拉的人拉低。

所以我们选择做拉人的人，还是被拉的人，还是推人的人？

可是为什么不是合力一起向前呢？

这又回到原点，两个人合在一起，

没有外界推手，在不光滑的世界里，只能停止了。

所以看起来是，贫富差距，高低贵贱，是内部推力造成的。

内部推力，造成了相反方向前进的人。

选择一起前行，需要相信自由意志，并把对方当做外部推手。

请脑补，同事，同学，家人在这里的对方的意义。

然后才能在不光滑的世界向前进。

当然这只是我们自认为的前进。

因为在最外部的推手来看，方向是他决定的。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/15</guid><pubDate>Thu, 25 Feb 2021 13:38:16 +0000</pubDate></item><item><title>原则</title><link>https://www.jiamo.name/blog/blog/16</link><description>**原则零：我们需要原则，需要自己独立思考的原则。**

意识到没有人和事是不可替代的。意味着没有什么是值得和一日三餐做替换的。而且一日三餐也是可以被替代的。

**原则一：要么生存，要么不生存。**

对生活的非生存的需求，都是一种虚伪和虚无。精神粮食和精神鸦片就像吃肉一样。短暂的欢愉带来不了长久的平淡和平凡。最终吃肉也像吃素一样。

**原则二：不要在乎生存的形式。**

追求高水平的生存，就是要做事情。而高水平的生存涉及对意义的解构，只能是世俗化的。但也必须是自己承认的。所以摈弃虚无的方法就是做事。

**原则三：做新的事情，才能扩展生存状态。**

没有技艺的精深，不断尝试其他新的事情，也只能是浮于表面。

**原则四：清算旧事，才能新事。**

留恋过去，是认识不到现在。旧的事情怎么才算结束。又怎知不是进入了死循环。

**原则五：不反思就不会进步。**

做对了，怎么才能做的更好？做错了，为什么错了？意识到已经犯错，正在犯错，将来还会犯错。有人承认错误，是为了自己好受，内心却认为犯错的是他人，是世界 。承认错误的终极检验法则就是不会犯同一个错误。人就是在不断犯错误，有的人总是犯同一个错误，有的人犯着各种各样的错误。

**原则六：要诚实的面对自己，不要欺骗自己。**

人不知而不愠，不亦君子乎。所以你确实觉得自己做对了。别人会说你做错了。

**原则八：没有必要让全世界都理解你。**

你写了原则，是为了让其他人也遵循吗？

**原则九：未经审慎的他人经验，不可效仿。**</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/16</guid><pubDate>Thu, 25 Feb 2021 14:32:45 +0000</pubDate></item><item><title>论特权</title><link>https://www.jiamo.name/blog/blog/17</link><description>0\. 特权是特殊的权利、优势或豁免权，只有特定的个人或群体才能获得或享有

1\. 显示与众不同，显示优越，显示自己比别人强

2\. 仔细问一问，这是何苦。有的人会说，你是愿意喝汤，还是愿意吃肉。 你是愿意跪着吃肉，还是愿意站着吃肉。 你是愿意站着吃肉，还是愿意飘起来吃肉。一个吃怎么吃都是吃，在别人面前吃就是不一样了。

3\. 虚荣是为了取得表面上的荣誉和引起外界普遍的关注，而表现出的偏离常态的心理和动作。

4\. 哪里有特权。到处都有。

5\. 指桑骂槐和选择性批评都是不可取的。要做到一以贯之的逻辑。需要本质上认清自己的诉求和利益所在。

6\. 因为屁股的原因选择批判就是一种虚荣，因为爱这个世界而批判我是尊重的。我对爱这个世界的人持怀疑态度。

7\. 形式主义和特权有千丝万缕的关系。

8\. 权利怎么被关进笼子里。到处都是小权利，可是权利从哪来呢？**权利是一种社会力量碰撞产生的边界** 。

9\. 不想改变世界，也不想改变自己，避免被小权利恶心到，就是远离。那就是沉默的大多数了。

* * *

因为知识就是力量，就参与了社会力量的碰撞。先知的特权导致了人对知识的追求。 “从知识中找到了快乐, 找到了更高级的生存形态”。人们会在意有特权的人说这种话，认为他们的特权是因为他们的知识。

A: 自己偷着乐不行吗，非要说出来吗？

B: 说出来不行吗？难不成你没有快乐还不能让别人说了吗？

快乐可以来源于很多东西，不快乐也可以来源很多东西，为什么是知识，又为什么不是知识。特引爱因斯坦的一段文字。

&gt; “当我还是一个相当早熟的少年的时候，我就已经深切地意识到，大多数人终生无休止地追逐的那些希望和努力都是毫无价值的。而且，我不久就发现了这种追逐的残酷，这在当年较之今天是更加精心地用伪善和漂亮的字句掩饰着的。每个人只是因为有个胃，就注定要参与这种追逐。而且，由于参与这种追逐，他的胃是有可能得到满足的；但是，一个有思想、有感情的人却不能由此而得到满足。”

知识导致的特权，看起来好像和小区物业的特权不太一样。或者是规定哪里人不能坐那种舱位的特权。但知识到底产生了什么特权？为什么物业有权不让你进？为什么有物业？层层递进，为什么制度是这样？是知识才能设计出制度？谁的知识可以设计制度？目的是大多数人的利益，还是少数人的利益，还是某类人的利益？

思想看起来也是这样。有思想，有感情是另一种虚伪的掩饰。

创造一个芯片，和创造一双袜子的确又是不一样的。人的快乐和悲伤，总是只能被自己界定。价值的高低似乎决定了话语的分量。论特权似乎可以等价于论话语权，只不过特权给人感觉是物质上的，而话语权则是从精神层面考虑。

有些人愿意跟随做解释的人。有些人创造不是为了获取特权，而是为了追寻生存的意义。

* * *

我似乎在宣泄对 “**导师/先知/至高权利的人/话语权大的人/有众多追随者的人 ”** 一种负面感觉。但又觉得安安静静做一个普通人，似乎并不能发现所谓生存的本质。“乱花渐欲迷人眼，浅草才能没马蹄” 白居易这是真的醉心初春的景色？我认为这也是话语权的一种体现，通过诗篇表达豁达，而不是只从“诗言志” 变成了 “诗抒情”。

或者我只是简单的不相信有爱世界的人。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/17</guid><pubDate>Thu, 25 Feb 2021 16:07:21 +0000</pubDate></item><item><title>诗一首</title><link>https://www.jiamo.name/blog/blog/18</link><description>​昨晚和老婆一起泡脚，说想作诗一首，表达一下感情。但是才智实在有限，拼凑了一首假七律。

&gt; _日出而作日落归，总有闲事挂心头；_
&gt; 
&gt; _本是立志上青天，奈何踌躇思人间；_
&gt; 
&gt; _平平江水杨柳依，江边水上起歌声；_
&gt; 
&gt; _春来听雨秋来醉，悲欢离合总归一。_

简要解释一下

第一句算是日常状态，大概是些什么闲事呢？就是春歌里面 “春有百花秋有月，夏有凉风冬有雨，若无闲事挂心头，便是人间好时节” 的闲事。

第二句算是理想和现实的对比。上青天作甚，其实最后还是要看人间。但理想总是要有的。

第三句算是借鉴，骑车上班路上，经常有老大爷起歌声，有感而发，三国演义片尾曲有一句，“长江有意化作泪，长江有情起歌声” ，那里起的歌声和这里起的歌声又有什么分别？

第四句算是表达了一点愿景，为什么要归一，道生一，一生二，二生三，三生万物，万物有道，道就是一，一就是二，二就是三，三就是万物。所以归不归一，都是归一。借鉴蒋捷的听雨。“少年听雨歌楼上，红烛昏罗帐。壮年听雨客舟中，江阔云低，断雁叫西风。而今听雨僧庐下，鬓已星星也。悲欢离合总无情，一任阶前点滴到天明”。

如有雷同纯属巧合。

* * *

全文表达什么样的感情呢？

通读 [ https://ostrichprotocol.org/ ](&lt;https://https://ostrichprotocol.org/ &gt;)和 [https://missionprotocol.org/ ](&lt;https://missionprotocol.org/ &gt;)两篇文章。

我不确定我是真的理解了两篇文章的含义。也许就是英雄主义和个人主义的分歧。

就如我曾经思考的主题一样，我们要改变世界，我也想安安静静。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/18</guid><pubDate>Thu, 25 Feb 2021 16:12:14 +0000</pubDate></item><item><title>论美国选举</title><link>https://www.jiamo.name/blog/blog/19</link><description>![](/files/select.png)

A: “ 我不入地狱谁入地狱 ” 这是什么意思？

B: 人人都是傻逼。

A: “ 曹公不追关羽，陆逊不再攻刘备，其所见固同也。以智遇智，三国所以鼎立欤。” 这是什么意思？

B: 人人都不是傻逼。

用美国先贤的话怎么说

A: What's the meaning of “Of the people, by the people, for the people.”

B: The people are stupid.

A: What's the meaning of “You can fool all the people some of the time, some of the people all the time, but you cannot fool all the people all the time.”

B: The people are not stupid.</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/19</guid><pubDate>Thu, 25 Feb 2021 16:17:37 +0000</pubDate></item><item><title>拉祜族民歌一首</title><link>https://www.jiamo.name/blog/blog/20</link><description>​我一边在敲着键盘，不知道在干啥，

老婆躺在床上看 “姐姐的爱乐之旅”。

听到了这首曲子，曲风苍凉中透露出一种坦然的生命力量。值得记录一下。

歌词：

* * *

**我的父母很勤劳**

**但我的家还是很贫穷**

(交代原因，认为勤劳不应该贫穷，这是主人公长大之后回过头来看，才有此感想，至于对错放到一边)

**我年幼很渴望上学**

**放牛之余我会偷偷的**

**跑去教室外**

**听那浪浪读书声**

**感觉无比快乐**

(自己的喜好，听到读书声感到快乐，是一种喜欢的情感，虽不得，但知道自己喜欢）

**每一天的傍晚**

**当别人放学**

**我却还是那个**

**渴望读书的小牧人**

**不管风吹日晒**

**都在地里劳作**

(现实, 小牧人还要在地里劳作，并没有在地上打滚）

**我独自唱给牛儿听**

**唱给风听**

**唱给大自然听**

(豁达，才有这首曲子的诞生）

* * *

拒绝替父母分担，则会成为一个懒惰的人。

不只是替父母分担，还在思考自己的喜爱，终究书写了自己的命运。

* * *

歌曲最后透露出朴素的人与自然的和解。

毕竟现实还只是人类的世界，人最后还是会化为灰烬，回归自然。

所以我觉得良好的品质包括： 宠辱不惊，不以物喜，不以己悲，豁达，热爱生活，热爱自然等。

* * *</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/20</guid><pubDate>Fri, 26 Feb 2021 02:46:14 +0000</pubDate></item><item><title>短论</title><link>https://www.jiamo.name/blog/blog/21</link><description>工作前几年的感悟， 需要清理一下自己的内心偏执。

里面的话，里面每句话应该是经过深入思考过的。现在看来也比较精炼。

但好像意义又不多，更多的是助长了自己的偏执。

而没有很好的和别人交流，听取别人的建议。

认为每个人的行为必有动机，将会导致道德和法律的内在不一致性。

自我演进，应该持续接受外界因素的干扰，并进行过滤。

把所有“过程”去除之后，人类自己应该找不到自身的动机，除非这种过程，没有开端，也没有结束。

就个人而言，这种无动机普遍体现在“每个行为必然有动机”的执着之上。

法律和道德建立在这种执着之上后，始终无法做到内在的一致。

身处壮美之地的居民视为普通，但没见过的人感到震撼，事物的伟大往往在意念之中。

绘画的本质在于这种意念的表达，欣赏在于整个过程的审阅。

人事的伟大描述非宗教性的意义只有反讽。

科技进步的无止境和道德意义的进化应该会并行。

当日常之务回归日常时，渴求宁静便是首要。

可人终究是现象或事实。

恐惧源自自身，但大部分确是外力所致。

组织用恐惧来达到所谓的奋进，是组织自发的内部恐惧，而这种内部恐惧也是由于外力。

全体的失败在于个体本身无法摆脱的不完整性。

当别人言及整体的愚昧之时，个人往往以为自己是整体之外。

看到别人说是好的，美的，有意义的，也想去发现到底是不是好的，美的。

有的人喜欢表明喜欢，有的人不喜欢表明不喜欢。最怕的是不知道。

如果有完全的自觉，必然也有清晰的行为实践。

冲动和颤动不是一回事。

电视剧和电影也是不一样的。

记得大学语文老师的“乱谈竞判天和地”，再想想计算机界的语言。

可是没有人能充当上帝的角色惩罚程序员，混乱就是世界的本质。

一小部分人会去追求无法达到的统一。

现实的意义可能就是此刻的快乐，持续的快乐隐含着不自知。

程序员的无聊在于不必证明上帝的存在, 同样一小部分人会把自己当作上帝。

经常告诫自己，“不要在较为基础的技术问题上做一些愚蠢的重复劳动”, 等价于 “打杂”。

最怕的往往是已经发生的。喜欢用源头和意义来寻找说辞，甚至是拿“世界之无”来演绎。

难道真的喜欢编程？这只是一种按部就班之后的宿命？只是生存的手段？

在一连串选择中透露出的那种胆小，恐惧，无理性。 反抗本就是劣根。

现在来看，被赋予可以热爱一件事物的能力是多么的幸运。

如果智力是一种力，我相信人类社会也有一种能量守恒。

如果家族相似成立，家族史可能是抛物线，扩大到人类史，并不一定是前进的波浪式。

把上面两者结合起来看，好像是决定论。

“不可知”是大部分“无知，普通” 的认知极限。

“成功” 的思想和行为，始终携带“必将消逝”的因子，也无损它的成功。

斯巴达的立法者周游列国之后立下规则：公民不准长时间留在外邦。说是为了防止受影响。

人总是容易被外界力量所冲击，那么永恒的法则似乎不存在。

500年后斯巴达式微，只是因为引入了黄金？又或许人性已经被一种惯性压抑的太久了。

他本人的自我牺牲又证明了他的一致性和真诚。

有时候变的很沉默。人生凌驾于人性之上，容易傲慢；人性凌驾于人生之上容易偏见。

充满多样人性的多样人生既是自然的属性，也是个人的悲哀，又或者说是幸事。

偏执，可能并不是证明什么，只是不容易自制罢了。

反自由与自由，禁锢与反禁锢。善摧毁恶，恶摧毁善。冲突构成世界的本质。

普通的感动很容易瞬间石化。如同复杂的本质，多意味着相似，不相关意味着深度联系。

很难想象导演身体的变化，是否意味着本质的变化。

人理解的世界和世界本质差别太大。说 “你被禁锢了！”，你会信吗？

本是不一样的，可是环境让大家看起来一样，这种一样带来普遍的隔离。

若人不一样，团体的行成有助于理解。

让你承认，某的自由和某的自由在思想上和事实上是有本质的区别？

却不能将这种本质只是置于经验或理性之上。

毕竟时间看起来像是线性的。人也只是走不出去，走不出去。

“弑父”，“弑师”等被认为成一种心理的本能时，更多的是为了解释这种现象。

当有人说这种现象不可被言说时，行为的意义只能依从鉴赏的角度。

如同 “我爱吾师，我更爱真理”的效果一样。

又或许毁灭别人，是毁灭之前的自己，达到全新的手段。

假设目的是一种全新的自我，又可以看作是非我了。

逻辑的可用，很大程度上取决于它的一致性。

科学也好，逻辑也罢，加之数学始终是一种串行的推理链。即使反复也是为了更好的推进。

当有人质疑这链条的尽头时，实际上也许在质疑这链条的开端。

因为或许它们本是一种东西。所以很多人说过程是重要的，那是因为看不到两端的缘故。

没人能够证明荒谬的东西是荒谬的，也没人可以证明理性的东西是理性的。

就像用荒谬不能证明理性，理性也不能证明荒谬，因为两者具有同样的本质。

时下各种地方冒出的“理性”，换成“荒谬”也是可以的。

斯巴达盛行同性恋，但只是人的一个阶段，经历过被爱与爱之后，又可以娶妻了。

人既是制度的产物，同时又创造制度。

而制度又不是一层不变的，那么又是因为什么而改变？恐惧？还是愿景？或者都是。

但是这些动力是集体性的，还是个人的？

我看的到优秀，都是一种外化形式。但其实是内化而来。

我在思考纯粹，天赋和德性之时，我自认为那是学习的产物。

我之所以不相信天性之使然，因为它会是一种命定论。

但一种极致是一种勇气，又必然是不完美。

孟子说人性本善，荀子说人性本恶。《蓝白红三部曲之红》的博爱，人天性的怜悯之心。

任何一个人不能做别人的审判者。年轻的法官抛弃了自己的狗。后来还是拿回来了。

人性因博爱而伟大。

以教义的形式的规定东西，就有可能被推翻。

组织的最高境界就是没有明文，这种意义上经验法的意义就太重大了。

《蓝白红三部曲之白》的平等，权利和金钱带来不平等，掩盖不了人人平等的真实性，与无可辩驳性。

但是世界的人和世界秩序又恰恰是建立在这种权势结构上的不平等。

人应当追求人性平等。但人性平等必定或者可能带来权势的不平等是人的悲剧性，和无法逃脱的。

《蓝白红三部曲之蓝》中自由是什么？自己不忍心杀鼠是一种善良，又为什么借猫杀鼠？

自己放不下心中的爱情，却借助死去丈夫的情妇完成抛弃。

所谓自由，应首先有这种渴望，但却是靠外力来触发去争取？

而放弃的自由也是一种自由。

理性主义的可能缺点就是，当别人问及来源时，它可能来自于直觉。

诋毁从来不是证明的途径，脏话是诋毁的一种低级形式。

然而事实往往又是如此的模糊。模糊到只有用诋毁来证明自己。

用 A 去衡量 B 时，如果这是两个不同维度的东西，又怎么区分你的看法和我的看法谁对谁错。

然后你坚持是一种偏执或自欺，你放弃是一种妥协和无奈。

能够比较的，为什么还区分出不同的名字。

“无名天地之始，有名万物之母”，分化之后，即不能复合，男女不同，一二不同。

无病呻吟和成熟有着看似相同的血缘。

人都在某一时刻失去了爱的能力，如还希望着被爱，这种能力就尚未消失，

否则只不过是孤独地忍受会慢慢熄灭像火种的东西。

既然渴求什么，又为何表现出某种惆怅或者恨意，特别是在做某件事半途而废时。

决绝和追问自己是谁基本同构。

想象之始是欲望，欲望之始是身体，是环境。内心世界是个什么世界，是否坍塌于被谈及。

可以被演绎的是知识，经验的就是直觉的。即使分析的淋漓尽致，也不能创造出什么。

能够自省的可能是虚妄，为自省而虚妄是一种无聊。

解决问题依赖头脑清晰，而那又是问题之始。半毛钱的关系是一种什么关系？

编译器，OS 不是程序意义的识别者。

如果没用户去理解你的程序，程序的意义也就微乎其微。

程序创造者是否应该考虑意义的释读者。就如伟大音乐的创造是否考虑过听音乐的人。

编译器优化，解释，OS 的执行有时甚至改变了程序的意义，说明了这些领域的不足。

而伟大的事物不因释读者和时代而褪色，又是否矛盾。

想想每天放弃做的事情，想做的事情和正在做的事情。

想想要在一件事情上做到极致时，身体和思维的变迁。

对“价值” 有一种模离两可的认识，加上内心的狂妄又或者是脆弱，一个人的快乐是对的还是错的。

精神归属的实质是否只是现实的不作为，反抗这种实质是否又决定了内心世界的激荡？

音乐唯一的好处就是可以让人停止思考。

如同电影和电视剧一样。这就像毒品。只不过萎靡的不是身体。

如同只是思考，没有说出来和分享，就像死的东西一样。 创造和消灭的是一种存在感。

有人可以忍受，有人无法忍受。

每个人都有自己理解的神秘感，想说，不敢说，埋葬着自己也搞不清楚的自己。

无法将脑力持续投入一个领域，是身体素质的原因？

技术驱动力的来源有多大可能是自发的。无法体会什么是快乐，自然无法体会什么是原创。

评判的标准越高就越容易心生自卑。

荣誉感，信念又和意义有多大联系。

生存，生活是一种语境，生命是另一种，都无法摆脱被判定的框架，框架和意义相关。

尊重的前提是值的尊重，不应像不知道生活的意义也去生活。

物理空间，历史框架，社会习俗连同个体差异，导致相互理解的不可能。

某一状态应该是最优存在，如不然我为什么这样而不那样。

完全机械或自然的推理决定行为的原因，反而忽视了行为的本质。

逻辑不是万能的，无序是一种无根，边界不是一种秩序。

大部分的表达，分主体的自我性。

理解你将成为什么，和表达你现在是什么基本同构。

含混的表达意味着内部的涵拟，最明显的表现形式是总是他在的表达，而非自在的表达。

要么是琐碎之事，要么总是自相矛盾.

大部分人认为生活，工作，学习不同。 没有看到内在的一致性和统一性，很容易角色错位。

以为没有建立在辛劳之上的知识世界是一种罪。

原初的思想很多源于人生体验。

我们在我们或他们给自己定义的框架之中，这种框架的预设，带来普遍的落差。

换个思路，怎样提升人生的快乐：首先体验人之辛劳。

西方和这边的相同点是生活或生命的意义不再那么显而易见。

不同点是那边更多的是意义的探索，而这边更多的是意义的接受。接受人家已经探索的意义。

&lt;广告狂人&gt;比&lt;摩登家庭&gt;某种程度上说，更多的反思了世俗生活的意义。

探索未知的原动力始终是现实已没有一个绝对的秩序。 这难道就是传说中的原力?

想象一下，可能有人在临行前，要求把他们头发摆弄的好点。

人们总会假定你放弃了别人的尊严，一定是先放弃了自己的。 事实上他们没有。

真正有尊严的绝不会置别人的尊严于不顾。 然后施与尊严的人，恰恰是为了证明自己的尊严。

事实上尊严这种东西完全是自发的。 看到这一点之后，死后破像更多的是笑话了。

至今对狗厌恶至极。

最近一次沿着马路独自走路时，突然旁边花丛跳出一条狗，我本能的后退并喔了一声，瞬间有点恐惧，

那条狗也是后退并叫了一声。

小时候建立的记忆并导致的喜好憎恶根深蒂固。

应该同这种在整体思维没有建立起来之前的记忆图景作斗争，不断减少思维定势和偏执。

博爱是一种人无法到达的境界。

做一个事情有趣，在做的过程中可以完全忘记其他，不知不觉。

以为一个事情有趣，为之做准备时不够深入的分析，然后中途发现另一东西有趣，不知不觉。

没有什么有趣，努力去找有趣的东西，不知不觉。

新的总将会变成老的，不知不觉的没有乐趣。

可是人是为了有趣，还是找有趣。

人是人类，还是人类是人。

视频可以自动提供字幕了，这么说中文也应可以。 自动翻译也应可以。

未来在大脑置个芯片什么的，将英语可以100%转为中文，也不用学英文了。

可是英语和中文是否完全同构？

描述能力最终取决于描述的事物，事物可能依旧是内心世界的外延而已。

“知识分子”都或许在某种程度上吸着“鸦片”。

某宗有“道得一斧，道不得一斧”之说。

空和迷思有相近的血缘，汉-宋-明更多的是迷思，而丢调某种“格物致知”。

至于杀戮，杀一和杀百的实质区别，以及赎对罪，对己的功能性作用。

而我只是愚蠢的度过了昨天，也将有今天。

至于说和不说，时不时的说说最好。

西学也好，中学也罢，修身也好，治国也罢，有可能没有真理，思考它才是真理。

有可能生活本就是个坑，生活就是生活的实质。

又至于做何生，做何想，若不是全然的随机性，那就把握一下那些确定性的东西。

我一再强调的愚蠢，正是努力要减少的东西。

比如像早睡一点，更尽力一点，更愿意去把握问题的实质，再细致一点，再极致一点。

最近同事说我有强迫证，那就在慢慢克服一点。

生活或许应该回归于常识，少一点大义。颤动毕竟是不能持久的。

追求思维的清晰也应该是成长的要素。还有就是要识别出自己逻辑中的矛盾之处，这很难。

昨天看淮阴侯列传。韩信评项羽，匹夫之勇又兼妇人之仁，真是精辟。

司马迁说刘邦见信死，既喜且伤。 是何用意？

又曰，其无钱葬母之时，将其放置高阔地带，后其母墓自有体魄。

又见陈平少时说“我执掌天下，也能分配均匀”。

大丈夫胸怀天下，可短于一时，又岂能短一世。 倘若是不得善终，肯定也不是天意。

我发现我并不太关心真相。但这和追求真理是不矛盾的。

因为真理并不一定在真相之中。 但是真理绝对不在假象之中。

所以一切现象，或不能激发我的探知欲。

倘若这种现象发现在自己身上，更懒得去辨别哪个是真的，哪个是假的。

从而不严肃变成一种假正经，或者说一种低迷。

自由的权利是一种选择权。

记得有个电影《飞跃疯人院》把正常人治成脑瘫，《发条橙》中牧师对“恶”的选择权的解释。

但是凭借什么去选择？真正意义上得选择来源于本性，或者是教育？

而教育的本质，是激发一种选择意识的主动性还是受教育的一致性？

一个乞丐也大声可以说“我存在！” ，尽管用‘也’字，很违心。 我如说“我依旧存在”岂不更好。

我现在看好的古人之一就是张良，倒不是他的事功，而是他的成长经历。 由儒入道产生的巨大能量。

尽管自称帝王师，内心是多么的平静。我也看到理性与血性的交融是多么的壮美。

当我知道它的力量之后，我刻意去寻求是很傻，傻着去追寻着。

信仰者可以评论无神论者，像鸵鸟把头埋进沙坑里。

无神论者可以笑到，“我不承认你想的就是真的，同时你不能强迫我让我相信它是真的！”。

但这种选择的自由， 最核心的内核不是知识。 而是经验和环境？

若‘无’也是一种背反的‘有’，选择也就只是昭示存在而已。

独立的选择意味决裂。否则就不需要了。

做别人要你做的事，是一种奴性。

但或许你觉得这是你的责任，是你应该做的。又或许觉得，这是你喜欢做的，是你选择的。

但终究一辈子下来，你没有发现什么是彻底自发做的，不考虑任何人类的意愿。

然后你觉得生命里只有生存，娱乐总是有别人的参与。不被描述的自我娱乐和可被展示的娱乐不同。

描述本身就是罪。

想当上帝，可是太难； 就想当圣人，也很难。想当皇帝，可是太难，就想当夫子，也很难。

有的人讲话，从来意识不到到自己可能的错误，偏见，信口开河。有的人讲话畏首畏尾，太过敏感别人的话。

自设角色，但角色是被他人评判和界定。不设角色，又怎能与他人相处。

独处时能体会的快乐和悲伤，是个人本性之所在</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/21</guid><pubDate>Fri, 26 Feb 2021 03:14:01 +0000</pubDate></item><item><title>ZMQ 并发服务器</title><link>https://www.jiamo.name/blog/blog/22</link><description>主要设计思路

![](/files/zmq_server.png)

worker 的 socket 与 worker_backend 沟通, client 的 sockert 与 client_front 沟通。

主要通过 handler 来处理。

这样做的好处： 参见 &lt;https://zguide.zeromq.org/docs/chapter3/&gt;

&gt; It gives us asynchronous clients talking to asynchronous servers, where both sides have full control over the message formats.
&gt; 
&gt; Because both DEALER and ROUTER can work with arbitrary message formats, if you hope to use these safely, you have to become a little bit of a protocol designer. At the very least you must decide whether you wish to emulate the REQ/REP reply envelope. It depends on whether you actually need to send replies or not.

好处是不用 REP 和 REQ 模式下，必须 send 和 recv 结对出现。

坏处是不好理解，broker 的方式有点难以调试。

具体参见实现： &lt;https://github.com/jiamo/zmq_server&gt;</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/22</guid><pubDate>Fri, 26 Feb 2021 07:09:16 +0000</pubDate></item><item><title>归并排序的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/23</link><description>本文为 [快速排序迭代版和递归版](&lt;https://www.allstoalls.com/blog/blog/3&gt;) 的归并版本。

要点是 merge 的写法，同时支持迭代和递归

递归版：

```python
def merge(S1, S2, result, result_start):
    start, end1, end2 = 0, len(S1), len(S2)
    x, y, z = start, start, result_start
    while (x + y) &lt; end1 + end2:
        if (y == len(S2)) or (x &lt; len(S1) and S1[x] &lt;= S2[y]):
            result[z] = S1[x]
            x += 1
        else:
            result[z] = S2[y]
            y += 1
        z += 1

def merge_sort_recursive(S):
    n = len(S)
    if n &lt; 2: return                
    mid = n // 2
    S1, S2 = S[0:mid], S[mid:n]
    merge_sort_recursive(S1)          
    merge_sort_recursive(S2)        
    merge(S1, S2, S, 0)            
```

迭代版（同一 merge）

```python
def merge_sort_iter(S):
    n = len(S)
    if n &lt; 2: return
    logn = math.ceil(math.log(n, 2))
    src, dest = S, [None] * n                  
    for i in (2**k for k in range(logn)):      
        for j in range(0, n, 2*i):            
            merge(src[j:j+i], src[j+i: min(j + 2 * i, len(src))], dest, j)
        src, dest = dest, src
    if S is not src:
        S[0:n] = src[0:n]
```

其中 desc 和 src 的交换是作为空间复用。merge 函数 result_start 参数纯属为迭代函数准备。所以好的公共函数，有助于 迭代到递归的转化。

&gt; merge(src[j:j+i], src[j+i: min(j + 2 * i, len(src))], dest, j)

中 i 为 step， 一次比较几个元素， j 是这一轮 merge 的起点。

函数是不是确定实现了，没有没有其他 bug。 暂未详细研究。只是用了下面的代码来检验：

```python
a = random.sample(range(30), 30)    
merge_sort_iter(a)     
assert a == list(range(30))
```

* * *

本着认真负责的态度，加强了验证。

```python
from hypothesis import given, strategies as st

@given(st.lists(st.integers()))
def test_merge_sort_iter(numbers):
    merge_sort_iter(numbers)
    assert all(x &lt;= y for x, y in zip(numbers, numbers[1:]))
```

说明算法基本上是正确的。 hypothesis 只是构建了部分输入集，并不能完全证明算法实现没有任何问题。但比普通单元测试靠谱。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/23</guid><pubDate>Fri, 05 Mar 2021 10:03:19 +0000</pubDate></item><item><title>生活的模式</title><link>https://www.jiamo.name/blog/blog/24</link><description>A:

昨天的很悲伤，已经过去了；

今天的最悲伤，就是太短暂；

明天的会悲伤，还没有到来。

B:

你这是自虐。

A:

昨天的很美好，已经过去了；

明天的会美好，还没有到来；

今天的最美好，就是太短暂。

B:

你这是自恋？

A:

昨天的很平常，已经过去了；

明天的会平常，还没有到来；

今天的最平常，就是太短暂。

B:

你这是自轻？

* * *

A:

昨天的很美好，已经过去了；

今天的最平常，就是太短暂；

明天的会悲伤，还没有到来。

B:

这是怀恋过去？

A:

昨天的很悲伤，已经过去了；

今天的最平常，就是太短暂；

明天的会美好，还没有到来。

B:

这是憧憬未来？

* * *

A:

昨天的很悲伤，已经过去了；

今天的最美好，就是太短暂；

明天的会平常，还没有到来。

B:

这是及时行乐？

A:

昨天的很平常，已经过去了；

今天的最美好，就是太短暂；

明天的会悲伤，还没有到来。

B:

这是守成难？

* * *

A:

昨天的很美好，已经过去了；

今天的最悲伤，就是太短暂。

明天的会平常，还没有到来；

B:

这是浪子回头？

A:

昨天的很平常，已经过去了；

今天的最悲伤，就是太短暂。

明天的会美好，还没有到来；

B:

这是要延迟满足？

* * *

为什么不继续组合？

其他组合可以被涵盖。

为什么写下这个对话。

大概是想说，无法选择的，需要客观判断，能够选择的，需要主观能动。

人生可能就是会有一种模式。

把每天当作一个长长的实验场景，能选择才是人的伟大或渺小的最真实的内核。

三个时间点描述，透露出淡淡的功利心以及以结果为导向的生命观。

三种状态描述，透露出人生百态终不过在这三种状态上切换。

* * *</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/24</guid><pubDate>Tue, 09 Mar 2021 14:56:13 +0000</pubDate></item><item><title>Flink Job 提交过程路径整理</title><link>https://www.jiamo.name/blog/blog/25</link><guid isPermaLink="false">https://www.jiamo.name/blog/blog/25</guid><pubDate>Fri, 12 Mar 2021 10:40:54 +0000</pubDate></item><item><title>论婚姻以及其他相关问题</title><link>https://www.jiamo.name/blog/blog/26</link><description>活出人样

* * *

A: 自己活不出个人样，两个人一起活也不会有什么人样。

B: 自己已经活出个人样，也没有必要和别人一起了。

A: 所以最好是两个将要活出人样的人，一起活出人样？

B: 那两个一起活出人样之后呢？

A: 历史往复，可能变成一个人不成模样？

B: 所以需再找一个将要活出人样的人，一起活出人样

鲁迅的爱

* * *

以前我说过，鲁迅可能是爱的太深，到死才一个不宽恕。

现在慢慢觉得，敢爱敢恨是立人的关键。

所以不宽恕是立。《绝命律师》马上就要开播了。

亲兄弟明算帐的时候怎么可能谈宽恕。

天才和庸人

* * *

脾气再好，庸人还是庸人。

脾气不好，天才还是天才。

脾气再好，天才还是天才

脾气不好，庸人还是庸人

哲学家

* * *

你娶到一个好妻子，你会很幸福;

你娶到一个糟糕的妻子，你会成为哲学家。

你以为你已经是哲学家了，会得到幸福。

但事实上你既不是哲学家，也没有幸福。

科学家

* * *

你嫁给一个好丈夫，你会很幸福;

你嫁给一个糟糕的丈夫，你会成为科学家。

你以为你不是科学家了，不会得到幸福。

但事实上你既是科学家，也得到了幸福。

活出自己的天命

* * *

和自己喜欢的人结婚。

和自己喜欢的人共事，

做自己喜欢做的事情。

听起来都很美好。

做起来其实更美好。

The book of why ｜ 找一下不好好吃饭的理由

* * *

今天的西红柿是如此的鲜。

我不吃，又有什么意义。

多少人在一日三餐中败阵下来。

避免败阵的最好途径

就是拒绝和一日三餐做斗争。

我可以不吃不喝三天。

不是因为不怕死。

而是因为多吃一顿饭，

并不会让我活的更久。

论宽恕

* * *

能宽恕是一种能力。

选择宽恕是升华。

选择宽恕别人，但不宽恕自己。

是因为太不喜欢自己。

可是终究还是有喜欢的人和事。

宽恕自己也是为了宽恕别人。

爱和烹饪

* * *

If you work for a living, 

why do you kill yourself working?

If you hate for a cooking,

why do you keep cook hating?

If you sad for a loving, 

why do you left love sading?

​</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/26</guid><pubDate>Sat, 13 Mar 2021 13:46:45 +0000</pubDate></item><item><title>全排列的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/27</link><description>递归版 参考： &lt;https://dotkay.github.io/2017/10/06/computing-permutations/&gt;

```python
def permute_rec(s, start, end):
    c = []
    if start == end:
        return [list(s)]
    else:
        for i in range(start, end + 1):
            s[start], s[i] = s[i], s[start]  # 不交换，依次和后续每个元素交换
            t = permute_rec(s, start + 1, end)
            c.extend(t)
            s[start], s[i] = s[i], s[start]
        return c
```

迭代版和快速排序的迭代版几乎是一样的。 &lt;https://www.allstoalls.com/blog/blog/3&gt;

```python
def permute_iter(s):
    c = []
    lo = 0
    hi = len(s) - 1
    range_queue = Queue()
    range_queue.put([lo, hi, list(s)])
    while not range_queue.empty():
        lo, hi, r = range_queue.get()
        if lo == hi:
            c.extend([r])
        for i in range(lo, hi+1):
            r[lo], r[i] = r[i], r[lo]   
            # 不交换，依次和后续每个元素交换 和递归版本的概念一样
            range_queue.put([lo+1, hi, list(r)])
            r[lo], r[i] = r[i], r[lo]
    return c
```</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/27</guid><pubDate>Fri, 26 Mar 2021 10:19:28 +0000</pubDate></item><item><title>八皇后问题的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/28</link><description>递归算法来源 《Combinatorial generation》，是我见过的最优雅的八皇后解法了。书里对应的通用迭代回溯算法，实在没有转化成功。但在理解递归解法之后，还是把拼图完成了。

递归版；

```python
a = [True for _ in range(0, 9)]   # begin a[1]
b = [True for _ in range(0, 18)]  # begin b[2]
c = [True for _ in range(0, 16)]  # -7 to 7
x = [0 for _ in range(0, 9)]
g = 0

def print_queen():
    for i in range(1, 9):
        for j in range(1, 9):
            if x[j] == i:
                print("Q  ", end="")
            else:
                print("*  ", end="")
        print("\n")
    print("------------  count:%d--------------", g)

def queen(col):
    global g
    # eight queue question
    for row in range(1, 9):
        if a[row] and b[row + col] and c[row - col]:
            x[col] = row
            a[row] = b[row + col] = c[row - col] = False
            if col &lt; 8:
                queen(col + 1)
            else:
                g += 1
                print_queen()
            a[row] = b[row + col] = c[row - col] = True

if __name__ == "__main__":
    queen(1)
```

    
    a: indicate if a row does not contain a queue
    b: indicate if a diagonal does not contain a queue
    c: indicate if a diagonal does not contain a queue (opposite direction from b)

放置好了一个皇后之后，它的攻击位置也就确定了。

a[row] 表示这一行不能再放。

b[row+col] 表示下一列放置时，现在位置的右上角不能放 ( 判断时， row -1 + col + 1 = row + col) 

c[row-col] 表示下一列放置时，现在位置的右下角不能放（判断时， row + 1 - (col + 1）= row - col)

迭代版：

```python
def queen_iter():
    global g
    col = 1
    while col &gt; 0:
        for row in range(x[col] + 1, 9):
            if a[row] and b[row + col] and c[row - col]:
                x[col] = row
                a[row] = b[row + col] = c[row - col] = False
                if col == 8:
                    g += 1
                    print_queen()
                    a[row] = b[row + col] = c[row - col] = True
                else:
                    col += 1
                    break
        else:
            x[col] = 0
            col = col - 1
            a[x[col]] = b[x[col] + col] = c[x[col] - col] = True
            continue
```

要点：

1\. print_queen 之后，赋值是恢复。已经成功了之后，它的占用不再需要。

2\. for 循环之后的 else。表示需要回溯了(col = col -1)。 但回溯了，说明之前的设置占用位置需要重置。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/28</guid><pubDate>Sun, 18 Apr 2021 13:13:43 +0000</pubDate></item><item><title>笛卡尔积的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/29</link><description>启发自： Mary Sheeran, Chalmer 的演讲 In praise of Higher Order Functions and of some friends and heroes 。我觉得推导的不直观。而且跳跃性太大。自己重新推导了一遍。

递归版

```
cartProdN1 :: [[a]] -&gt; [[a]]
cartProdN1 [] = [[]]
cartProdN1 (xs:yss) = [ x:ys | x &lt;- xs, ys &lt;- cartProdN1 yss ]
```

迭代版：

```
cartProdN9 :: [[a]] -&gt; [[a]]
cartProdN9 xss = 
 foldr h1 [[]] xss where 
  h1 xs yss = foldr g [] xs where
     g x zss = foldr f zss yss where 
         f xs yss = (x:xs):yss 
 
```

其中 h1 一个一个处理 xs 和 聚合结果。

然后 xs 又是一个 list 。对 xs 而言也需要 遍历处理。然后处理 xs 的每一个元素的时候。

有人可能会说 foldr 怎么就是迭代解法。我们认为 foldr 可以用 foldl 实现。 然后 foldl 是尾递归。可以等价为迭代处理。不是本文的主题，暂且不说。

* * *

推导如下：

```
cartProdN1' :: [[a]] -&gt; [[a]]
cartProdN1' xss = 
   h xss where 
      h [] = [[]]
      h (xs:yss) = [ x:ys | x &lt;- xs, ys &lt;- h yss ]  
```

化 haskell 的模式匹配到一个函数。起名 cartPordN1'， 因为比较直观没有用到什么技巧。

提取 (h yss) 为 yss 也比较直观

```
cartProdN1'' :: [[a]] -&gt; [[a]]
cartProdN1'' xss = 
   h xss where 
      h [] = [[]]
      h (xs:yss) =  g xs (h yss) where  
          g xs yss = [x:ys | x &lt;- xs, ys &lt;- yss]
```

看一下 foldr 的结构

```
foldr :: (a -&gt; b -&gt; b) -&gt; b -&gt; [a] -&gt; b
foldr f z [] = z
foldr f z (x:xs) = f x (foldr f z xs)
```

h 遍历 xss 和 foldr f 结构一样。

```
cartProdN2 :: [[a]] -&gt; [[a]]
cartProdN2 xss = 
  foldr h [[]] xss where 
    h xs yss = [x:ys | x &lt;- xs, ys &lt;- yss]
```

列表展开的定义 。 第一个元素和 后面所有的匹配。加上后面整体的结果。

```
cartProdN3 :: [[a]] -&gt; [[a]]
cartProdN3 xss = 
  foldr h [[]] xss where 
    h (x:xs) yss = 
      map (x:) yss ++ [x:ys| x &lt;- xs, ys &lt;- yss]
```

然后可以识别出列表推导就是 h 自己的一个调用 [x:ys| x &lt;\- xs, ys &lt;\- yss] = h xs yss 。而且替换成 h ，h 变成了递归函数，必须要有 base case。

```
cartProdN4 :: [[a]] -&gt; [[a]]
cartProdN4 xss = 
 foldr h [[]] xss where 
  h [] yss = [] 
  h (x:xs) yss = (map (x:) yss) ++ (h xs yss)
```

使用演讲中的 mapa

```
mapa :: (b -&gt; a) -&gt; [a] -&gt; [b] -&gt; [a]
mapa g z l = (map g l) ++ z
```

替换得到

```
cartProdN5 :: [[a]] -&gt; [[a]]
cartProdN5 xss = 
 foldr h [[]] xss where 
  h [] yss = []
  h (x:xs) yss = mapa (x:) (h xs yss) yss
```

观察 mapa 的结构 。 z 当作累积参数可以提前被追加。

```
mapa :: (b -&gt; a) -&gt; [a] -&gt; [b] -&gt; [a]
mapa g z l = foldr f z l where
    f x y = (g x):y 
```

替换 mapa 为 foldr 得到。

```
cartProdN6 :: [[a]] -&gt; [[a]]
cartProdN6 xss = 
 foldr h [[]] xss where 
  h [] yss = [] 
  h (x:xs) yss = foldr f (h xs yss) yss where
    f xs yss = (x:xs):yss  
```

其中 mapa 中的 g = (x:) ; x = xs ; z = yss; z = (h xs yss); y = yss (这是 f 的参数 yss ，注意和 foldr 的参数 yss 区别)

这里并没有结束。**h 这里递归函数。并不是迭代。我们的目的是消除递归调用。**

消除递归调用 h 的方法。就是 h 提取出来。引入辅助函数。

```
cartProdN7 :: [[a]] -&gt; [[a]]
cartProdN7 xss = 
 foldr h [[]] xss where 
   h xs yss = h' xs
    where
      h' []     = []
      h' (x:xs) = foldr f (h' xs) yss where 
        f xs yss = (x:xs):yss 
```

cardPord6 和 cardProd7 为什么等价。 **这一块没有想好怎么描述。 看起来也不直观。 等待后续继续观察。。。**

继续引入辅助函数 g . 这一步相对直观， 就是把 foldr f 抽离出来。

```
cartProdN8 :: [[a]] -&gt; [[a]]
cartProdN8 xss = 
 foldr h1 [[]] xss where 
  h1 xs yss = h' xs
   where
    h' []     = []
    h' (x:xs) = g x (h' xs)
    g x zss = foldr f zss yss
      where 
        f xs yss = (x:xs):yss     
```

可以发现。 h' 本身就符合 foldr 的结构

```
cartProdN9 :: [[a]] -&gt; [[a]]
cartProdN9 xss = 
 foldr h1 [[]] xss where 
  h1 xs yss = foldr g [] xs where
     g x zss = foldr f zss yss where 
         f xs yss = (x:xs):yss 
```

**g 就是 h ' foldr 化的 参数 f 。**

* * *

cardProdN6 -&gt; cardProdN7 还是想解释一下。但发现自己只能用 python 描述了。

```
def f(xs, yss):
    if xs == []:
        return []
    x xs &lt;- xs
    return foldr g (f xs) yss

def f(xs, yss):
    def f'(xs):
        if xs == []:
            return []
        x xs &lt;- xs
        return foldr g (f' xs) yss
    return f' xs
```</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/29</guid><pubDate>Tue, 25 May 2021 16:15:13 +0000</pubDate></item><item><title>子集的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/31</link><description>Combinatorial Generation 这书读的还是太慢，要想弄懂还是要下大功夫。从原书的得到

递归版：

```
def subset_rec0(l, k, res):
    if k &gt;= len(l):
        print(list(filter(None, res)))
        return

    res[k] = None
    subset_rec0(l, k + 1, res)
    res[k] = l[k]
    subset_rec0(l, k + 1, res)

#这样使用
subset_rec0([1, 2, 3], 0, [None, None, None])
```

相关理解：

书中用 0 1 的 lexicographic order 来解释。得到输出结果 **（为了说明算法的简便， 假定数组元素 不为 0， 不重复）**

```
[]
[3]
[2]
[2, 3]
[1]
[1, 3]
[1, 2]
[1, 2, 3]
```

从 000 开始 ，后面慢慢加 1。 000-&gt;001-&gt;010-&gt;011-&gt;100-&gt;101-&gt;110-&gt;111

前四个就是**最高位位取零** 之后的总的递归结果。比较好理解。

产生对应的迭代版。书中 Next 算法，假定最高位为 0，是没有必要的。只需变书中 k = 0 为 k &lt; 0 可以直接产生整体结果。

```
def subset_iter0(l, res):
    length = len(l)
    while True:
        print(list(filter(None, res)))
        k = length - 1
        while res[k]:
            res[k] = None
            k -= 1
        if k &lt; 0:
            break
        res[k] = l[k]

subset_iter0([1, 2, 3], [None, None, None])
```

算法解释起来比较费劲，只能想象 000-&gt;001-&gt;010-&gt;011-&gt;100-&gt;101-&gt;110-&gt;111 这个过程。 每次迭代都从 length -1 开始**。认为 length-1 是低位** 。里面的 while 和 res[k] = [k] **表示了一种 + 1 的动作。 低位变为 0 ， 高位 + 1 。 01 变为 10。 这就是 为什么 res[k] = None 的原因。（牢记 length-1是低位, 所以 k -= 1 是为了进位, res[k] = l[k] 变 1，这里表示对应位置的元素参与结果）**

解释起来还是很不好理解。

* * *

正式因为不好理解，所以需要反复练习。为什么要用 rec0 和 iter0 。因为还有同类项。

很明显递归的方法中，**交换一下赋值顺序也是完全正确的解法：**

```
def subset_rec1(l, k, res):
    if k &gt;= len(l):
        print(list(filter(None, res)))
        return
    res[k] = l[k]
    subset_rec1(l, k + 1, res)
    res[k] = None
    subset_rec1(l, k + 1, res)

subset_rec1([1, 2, 3], 0, [None, None, None])
```

得到这样的结果

```
[1, 2, 3]
[1, 2]
[1, 3]
[1]
[2, 3]
[2]
[3]
[]
```

这样看起来就是完全逆向的计算的感觉。**从 111 - &gt; 110 -&gt; 101 -&gt; 100 -&gt;011-&gt; 010-&gt;001-&gt;000**

这就是减一。同样也是有迭代的解法。

```
def subset_iter1(l, res):
    length = len(l)
    while True:
        print(list(filter(None, res)))
        k = length - 1
        while k &lt; length and (not res[k]):
            res[k] = l[k]
            k -= 1
        if k &lt; 0:
            break
        res[k] = None

subset_iter1([1, 2, 3],  [1, 2, 3])
```

**这里 length - 1 依旧是低位** 。 里面的 while 和 res[k] = [k] 最后 res[k] = None **表示了一种 - 1 的动作。 低位变为 1 ， 高位 0 。 10 变为 01。**

这个解法应该是很容易从 subset_iter0 演变过来。

* * *

一不做而不休，我想到了一种对偶的概念。**length - 1 可以变成最高位** 递归解法都是从 0 到 k 。我们可以从 k 到 0 。

递归版

```
def subset_rec2(l, k, res):
    if k &lt; 0:
        print(list(filter(None, res)))
        return

    res[k] = None
    subset_rec2(l, k - 1, res)
    res[k] = l[k]
    subset_rec2(l, k - 1, res)

subset_rec2([1, 2, 3], 2, [None, None, None])
```

迭代版

```
def subset_iter2(l, res):
    length = len(l)
    while True:
        print(list(filter(None, res)))
        k = 0
        while k &lt; length and res[k]:
            res[k] = None
            k += 1
        if k == length:
            break
        res[k] = l[k]

subset_iter2([1, 2, 3], [None, None, None])
```

迭代和递归相对前面的前两版改动非常小。得到结果

```
[]
[1]
[2]
[1, 2]
[3]
[1, 3]
[2, 3]
[1, 2, 3]
```

从**000 - &gt; 100 -&gt; 010 -&gt; 110 -&gt; 001-&gt; 101 -&gt;011-&gt;111 。所以我们看到还是 一直加一。**

* * *

那么同理。一直减一的 ： **111 - &gt; 011 -&gt; 101 -&gt; 001 -&gt; 110-&gt; 010 -&gt;100-&gt;000**

```
[1, 2, 3]
[2, 3]
[1, 3]
[3]
[1, 2]
[2]
[1]
[]
```

递归版 

```
def subset_rec3(l, k, res):
    if k &lt; 0:
        print(list(filter(None, res)))
        return

    res[k] = l[k]
    subset_rec3(l, k - 1, res)
    res[k] = None
    subset_rec3(l, k - 1, res)

subset_rec3([1, 2, 3], 2, [None, None, None])
```

迭代版

```
def subset_iter3(l, res):
    length = len(l)
    while True:
        print(list(filter(None, res)))
        k = 0
        while k &lt; length and (not res[k]):
            res[k] = l[k]
            k += 1
        if k == length:
            break
        res[k] = None

subset_iter3([1, 2, 3], [1, 2, 3])
```

* * *

再不能创造知识的情况下，不停的重复，好像意义不是很大。**整个 加一， 减一， 递归，迭代， 高位， 低位，及其对偶的概念，重复，我感觉自己更理解了这个算法的本质。**</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/31</guid><pubDate>Sun, 06 Jun 2021 15:22:58 +0000</pubDate></item><item><title>组合的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/33</link><description>递归解法

```python
def comb(l, k, j, m, res):
    # we j start at 0
    n = len(l)
    if j &gt;= len(l):
        print(list(filter(None, res)))
        return
    if m &lt; k:
        res[j] = l[j]
        comb(l, k, j + 1, m + 1, res)
    if (k - m) &lt; (n - j):
        res[j] = None
        comb(l, k, j + 1, m, res)

comb([1, 2, 3, 4, 5], 3, 0, 0, [None for i  in range(5)])
```

迭代解法

通用的打印方法

```python
def print_res(l, res, k):
    ans = [l[res[i] - 1] for i in range(1, k+1)]
    print(ans)
```
```python
def comb_iter1(l, k, j, res):
    n = len(l)
    while True:
        j = k
        print_res(l, res, k)
        while res[j] == n - k + j:
            j = j - 1
        res[j] = res[j] + 1
        if j == 0:
            break
        for i in range(j + 1, k + 1):
            res[i] = res[i-1] + 1

comb_iter1([1, 2, 3, 4, 5], 3, 3, list(range(0, 5)))
```

书中说 : The while loop of Algorithm can be eliminated by making j a global variable (initialized to k) and observing that **the rightmost changeable position is k if res[k] &lt; n and is one less than its previous value if res[k] = n.**

优化版：

```python
def comb_iter2(l, k, j, res):
    # init item in res &lt; 0
    n = len(l)
    while True:
        print_res(l, res, k)
        res[j] = res[j] + 1 # 直接开始加 
        for i in range(j + 1, k + 1):
            res[i] = res[i-1] + 1
        if j == 0:
            break
        if res[k] &lt; n:
            j = k  # &lt; 5 说明可以继续从这里加
        else:
            # 加到 5 之后，第一位需要舍弃 需要在下一轮 + 1
            j = j - 1

comb_iter2([1, 2, 3, 4, 5], 3, 3, list(range(0, 5)))
```

问题： 

1\. 为什么要传 list(range(0, 5))?

2\. res[i] = res[i-1] + 1 是在做什么？

3\. res[j] = res[j] + 1 是在做什么？

不准备做详细解释。如果对照输出和课本理解此算法，自己会发现其精妙之处。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/33</guid><pubDate>Wed, 30 Jun 2021 16:13:19 +0000</pubDate></item><item><title>Monadic Compiler Calculation (ccc3) </title><link>https://www.jiamo.name/blog/blog/34</link><description>Graham Hutton 的 ccc 出了第三版 &lt;http://www.cs.nott.ac.uk/~pszgmh/bib.html&gt;

照例复制，并运行一番。

```haskell
module Main where

import Prelude hiding ((&gt;&gt;=), return)

data Expr = Val Int | Add Expr Expr | Throw 
  | Catch Expr Expr | Block Expr | Unblock Expr

data ND a = E | Ret a | ND a  :||:  ND a

g :: Show a =&gt;  ND a -&gt; String
g E = "E"
g (Ret x) = "RET" ++ show x
g  ( x  :||:  y) = (g x) ++ ":||:"  ++ (g y)

return :: a -&gt; ND a
return x = Ret x

reduce :: ND a -&gt; ND a
reduce E = E
reduce (Ret x) = Ret x
reduce  ( x :||:  E) = reduce x
reduce  ( x :||:  y) = (reduce x :||:  y)

(&gt;&gt;=) :: ND a -&gt; (a -&gt; ND b) -&gt; ND b
E &gt;&gt;== f = E
(Ret x) &gt;&gt;= f = f x
( x  :||:  y) &gt;&gt;= f = (x &gt;&gt;= f)  :||: ( y &gt;&gt;= f )

data Code = HALT | PUSH Int Code | ADD Code
 | THROW | MARK Code Code | UNMARK Code | BLOCK Code
 | UNBLOCK Code | RESET Code  deriving Show

compile :: Expr -&gt; Code
compile e = comp e HALT

comp :: Expr -&gt; Code -&gt; Code
comp (Val n) c = PUSH n c
comp(Add x y) c=comp x (comp y(ADD c))
comp Throw c = THROW
comp (Catch x y) c = MARK (comp y c) (comp x (UNMARK c))
comp (Block x) c = BLOCK (comp x (RESET c))
comp (Unblock x) c = UNBLOCK (comp x (RESET c))

type Conf = (Stack, Status) 
type Stack = [Elem] 
data Elem = VAL Int | HAN Code | STA Status deriving Show
data Status = B | U deriving Show

exec :: Code -&gt; Conf -&gt; ND Conf 
exec HALT (s, i) =  return (s, i)
exec (PUSH n c) (s, i) = exec c (VAL n:s, i) :||: inter s i
exec (ADD    c) (VAL n : VAL m : s, i) =  
  exec c (VAL (m + n):s, i) :||: inter s i
exec THROW (s, i)  = fail' s i
exec (MARK c1 c) (s, i) = exec c (HAN c1: s, i) :||: inter s i
exec (UNMARK c)(VAL n : HAN _:s, i) = exec c (VAL n:s,i)
exec (BLOCK c) (s, i) = exec c (STA i : s, B)
exec (UNBLOCK c) (s, i) = exec c (STA i : s, U)
exec (RESET c)(VAL v: STA i:s, _) = 
  exec c (VAL v:s,i) :||:  inter s i
exec _ _ = E

inter :: Stack -&gt; Status -&gt; ND Conf 
inter s B = E
inter s U = fail' s U

fail' :: Stack -&gt; Status -&gt; ND Conf 
fail'(VAL m:s) i = fail' s i
fail' (HAN c:s) i  = exec c (s,i)
fail' (STA i:s) _ = fail' s i 
fail' _ _ = E

main :: IO()
main =
  print (g (reduce (exec (compile ( Unblock (Add (Val 3) 
        (Unblock (Catch Throw (Val 4)))))) ([] , B))))
```

Haskell 的基础还是很薄弱。体现在两个上问题：

1\. **data ND a = ∅ | Ret a | ND a ⊕ ND a 怎么弄。我用的 Haskell for Mac 不行。**

**2\. ( &gt;&gt;=) 去掉之后完全不影响程序。放到这里有什么用？**</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/34</guid><pubDate>Sat, 10 Jul 2021 16:40:50 +0000</pubDate></item><item><title>子集，组合，排列的 prolog 解法</title><link>https://www.jiamo.name/blog/blog/35</link><description>```prolog
strip_delete(X,[X|L],L).
strip_delete(X,[_|L],R) :- strip_delete(X,L,R).

just_delete(X,[X|T],T).
just_delete(X,[H|T],[H|NT]) :- just_delete(X,T,NT).

combination(0,_,[]).
combination(K, L, [X|Xs]) :-
	K &gt; 0,
	strip_delete(X,L,R),  
   	K1 is K-1,           % we already have choose one. still need K-1
   	combination(K1,R,Xs).% then choose K-1 from R to Xs

subset([], []).
subset([E|L], [E|S]) :- subset(L, S).
subset(L, [_|S]) :- subset(L, S).

perm([],[]).
perm(List,[H|Perm]) :-  perm(Rest,Perm), just_delete(H,List,Rest).
```

* * *

求出全部结果

```prolog
gen_sub(Set, Powerset) :- 
  setof(Subset, subset(Subset, Set), Powerset).
gen_com(Set, N, Powerset) :- 
  setof(Subset, combination(N, Set, Subset), Powerset).
gen_pem(Set, Powerset) :- 
  setof(Subset, perm(Subset, Set), Powerset).
```

* * *

gen_pem([1,2,3], L) 可以求出结果。

&gt; &lt;The Reasoned Schemer&gt; 告诉我们
&gt; 
&gt; The First Commandment : Within each sequence of goals, move non-recursive goals before recursive goals.

交换一下顺序

```prolog
perm1([],[]).
perm1(List,[H|Perm]) :- just_delete(H,List,Rest), perm1(Rest,Perm).
gen_pem1(Set, Powerset) :- setof(Subset, perm1(Set, Subset), Powerset).
```

* * *

探究一下 perm1 这里需要交换 Set 和 Subset 的原因。其中 我们按空格不断求解 :

perm(L, [1,2,3]) 可以成功。

perm([1,2,3], L) 求解出完全排列之后，不能停止。

perm1([1,2,3], L). 可以成功。

perm1(L, [1,2,3]). 只求解一个结果之后，不能停止

* * *

所以说什么导致了 prolog 不断的继续搜索。

规避这种搜索的有效方法，就是加上长度约束。

```prolog
same_len([], []).
same_len([_|T1], [_|T2]) :-
  same_len(T1, T2).

perm([],[]).
perm(List,[H|Perm]) :-  
  same_len(List, [H|Perm]),perm(Rest,Perm), just_delete(H,List,Rest).
```</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/35</guid><pubDate>Sat, 16 Oct 2021 12:24:20 +0000</pubDate></item><item><title>LCS 所有解的递归和迭代算法</title><link>https://www.jiamo.name/blog/blog/36</link><description>为了分析卸载用户公共行为，发现初级的解法并不能满足时间复杂度。

动态规划求解矩阵：

```python
def dp_loopkup(lx, ly, lookup):
    for i in range(1, lx + 1):
        for j in range(1, ly + 1):
            if X[i - 1] == Y[j - 1]:
                lookup[i][j] = lookup[i - 1][j - 1] + 1
            else:
                lookup[i][j] = max(lookup[i - 1][j], lookup[i][j - 1])
```

初级递归解法：

```python
def findLCS(X, Y):
    lx = len(X)
    ly = len(Y)
    lookup = [[0 for x in range(ly + 1)] for y in range(lx + 1)]
    dp_loopkup(lx, ly, lookup)

    def LCS(m, n):
        if m == 0 or n == 0:
            return {()}
        if X[m - 1] == Y[n - 1]:
            lcs = LCS(m - 1, n - 1)
            return {(*l, X[m - 1]) for l in lcs}
        if lookup[m - 1][n] &gt; lookup[m][n - 1]:
            return LCS(m - 1, n)
        if lookup[m][n - 1] &gt; lookup[m - 1][n]:
            return LCS(m, n - 1)
        top = LCS(m - 1, n, )
        left = LCS(m, n - 1)
        return top.union(left)

    r = LCS(lx, ly)
    return r
```

发现不对劲，长度大一点，算不出来。简单分析一下，加一个缓存。(常规操作)

```python
def findLCS(X, Y):
    lx = len(X)
    ly = len(Y)
    lookup = [[0 for x in range(ly + 1)] for y in range(lx + 1)]
    dp_loopkup(lx, ly, lookup)

    @lru_cache()
    def LCS(m, n):
        if m == 0 or n == 0:
            return {()}
        if X[m - 1] == Y[n - 1]:
            lcs = LCS(m - 1, n - 1)
            return {(*l, X[m - 1]) for l in lcs}
        if lookup[m - 1][n] &gt; lookup[m][n - 1]:
            return LCS(m - 1, n)
        if lookup[m][n - 1] &gt; lookup[m - 1][n]:
            return LCS(m, n - 1)
        top = LCS(m - 1, n, )
        left = LCS(m, n - 1)
        return top.union(left)

    r = LCS(lx, ly)
    return r
```

长度再一大发现： RecursionError: maximum recursion depth exceeded in comparison

不想动 python 只好转迭代。转迭代通用的技巧就是使用栈。

但具体问题怎么求解，其实并不是那么直观。反复试错得到：

```python
def findLCS_iter(X, Y):
    lx = len(X)
    ly = len(Y)
    lookup = [[0 for x in range(ly + 1)] for y in range(lx + 1)]
    dp_loopkup(lx, ly, lookup)

    def LCS(lm, ln):
        range_queue = deque()
        result = set()
        range_queue.append((lm, ln, ()))
        while len(range_queue) &gt; 0:

            m, n, common = range_queue.pop()
            if m == 0 or n == 0:
                result.add(common)
                continue
            if X[m - 1] == Y[n - 1]:
                range_queue.append((m - 1, n - 1, (X[m - 1], *common)))
                continue
            if lookup[m - 1][n] &gt; lookup[m][n - 1]:
                range_queue.append((m - 1, n, common))
                continue
            if lookup[m][n - 1] &gt; lookup[m - 1][n]:
                range_queue.append((m, n - 1, common))
                continue

            range_queue.append((m - 1, n, common))
            range_queue.append((m, n - 1, common))

        return result

    return LCS(lx, ly)
```

到这一步其实已经满足需求了至少可以求解了。

还是发现有点慢。只好再做一次 cache 了。

```python
def findLCS_iter(X, Y):
    lx = len(X)
    ly = len(Y)
    lookup = [[0 for x in range(ly + 1)] for y in range(lx + 1)]
    dp_loopkup(lx, ly, lookup)

    class RememberDeque(deque):
        def __init__(self):
            self.rember = set()
            super().__init__()

        def append(self, x):
            if x not in self.rember:
                self.rember.add(x)
                super().append(x)

    def LCS(lm, ln):
        range_queue = RememberDeque()
        result = set()
        range_queue.append((lm, ln, ()))
        while len(range_queue) &gt; 0:
            m, n, common = range_queue.pop()

            if m == 0 or n == 0:
                result.add(common)
                continue
            if X[m - 1] == Y[n - 1]:
                range_queue.append((m - 1, n - 1, (X[m - 1], *common)))
                continue
            if lookup[m - 1][n] &gt; lookup[m][n - 1]:
                range_queue.append((m - 1, n, common))
                continue
            if lookup[m][n - 1] &gt; lookup[m - 1][n]:
                range_queue.append((m, n - 1, common))
                continue

            range_queue.append((m - 1, n, common))
            range_queue.append((m, n - 1, common))

        return result
```

到此为止，我的认知上就只能这样了。

是否还有更好的方案呢？

更好的数据结构？

deque 和 cache 结合？

或者是其他什么约束条件，某些元组不用放入栈里？

* * *

findLCS_iter 可以找到所有的最大公共子序列。如果只需要一种，可以简便的从 lookup 重建出一个来。

```python
def rebuild(X, Y):
    lx = len(X)
    ly = len(Y)
    # we don't need special handle for first row
    lookup = [[0 for x in range(ly + 1)] for y in range(lx + 1)]
    dp_loopkup(lx, ly, lookup)
    l = lookup[-1][-1]
    m, n = lx, ly
    result = [None] * l
    index = l - 1
    while index &gt;= 0:
        if n &gt; 0 and lookup[m][n] == lookup[m][n - 1]:  # Move left
            n -= 1
        elif m &gt; 0 and lookup[m][n] == lookup[m - 1][n]:  # Move up
            m -= 1  # may be can both move up and left
        else:  # Move up-left
            result[index] = X[m - 1]
            index -= 1
            m -= 1
            n -= 1
    return result
```</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/36</guid><pubDate>Wed, 27 Oct 2021 02:52:47 +0000</pubDate></item><item><title>Laziness in Haskell</title><link>https://www.jiamo.name/blog/blog/37</link><description>对 Haskell 还是没有入门。在文章 &lt;https://www.allstoalls.com/blog/blog/29&gt; 中提到一句话

&gt; 有人可能会说 foldr 怎么就是迭代解法。我们认为 foldr 可以用 foldl 实现。 然后 foldl 是尾递归。可以等价为迭代处理。不是本文的主题，暂且不说

这真是一知半解，无知的话。

起因：那片文章最后的迭代 并不是像 《Combinatorial Generation》里面的迭代算法一样，只是 foldr 并不能直观看出是迭代算法。想看看是不是真的可以迭代出来。

* * *

文章最后得到：

```haskell
cartProdN9 :: [[a]] -&gt; [[a]]
cartProdN9 xss = 
 foldr h1 [[]] xss where 
  h1 xs yss = foldr g [] xs where
     g x zss = foldr f zss yss where 
         f xs yss = (x:xs):yss 
```

先讲 foldr 可以用 foldl 实现

foldl 的实现

```haskell
foldl f z []     = z
foldl f z (x:xs) = foldl f (f z x) xs
```

使用 foldl 实现 foldr 得到：

```haskell
foldr f e l = foldl (\g b x -&gt; g (f b x)) id l e
```

用这个来替换 cardProd9 中的 foldr 得到

```haskell
cartProdN10 :: [[a]] -&gt; [[a]]
cartProdN10 xss = 
 foldl (\g b x -&gt; g (h1 b x)) id xss [[]] where 
  h1 xs yss = foldl (\g b x -&gt; g (g1 b x)) id xs [] where
     g1 x zss = foldl (\g b x -&gt; g (f b x)) id yss zss where 
         f xs wss = (x:xs):wss
```

这是在太丑陋了。

在 《Thinking Functionally with Haskell》 中有提到

只要满足 

```haskell
f x g (y z) = g (f x y) z
f x e =  g e x
```

就有

```haskell
foldr f e xs = foldl g e xs
```

我没有实际验证。但这么对称直接改动得到 （实际证明结果集满足算法，但返回顺序和 foldr 版本不同。所以其实我没有利用上面的公式，而是用对称性概念）：

```haskell
cartProdN11 :: [[a]] -&gt; [[a]]
cartProdN11 xss = 
 foldl h1 [[]] xss where 
  h1 yss xs = foldl g [] xs where
     g zss x = foldl f zss yss where 
         f yss xs = (x:xs):yss 
```

这样清爽多了。 以为要得到了验证，准备收手。不成想：

```haskell
cartProdN11 [[1,2]| i &lt;- [1 .. 1000]]
```

这个直接把 Haskell for Mac 跑挂了。

但是 

```haskell
cartProdN9 [[1,2]| i &lt;- [1 .. 1000]] 
```

居然秒出。这有点出乎我的意料。一番搜索找到 &lt;https://wiki.haskell.org/Foldr_Foldl_Foldl%27&gt;

换用文中的 foldl'

```haskell
foldl' f z []     = z
foldl' f z (x:xs) = let z' = z `f` x 
                       in  z' `seq` foldl' f z' xs
```

但并没有解决问题。我依旧以为是 lazy 特性导致的问题。

继续搜索：&lt;https://www.fpcomplete.com/haskell/tutorial/all-about-strictness/&gt;

并采用其中的技术：

```haskell
{-# LANGUAGE BangPatterns #-}

module D where
    
data StrictList a = Cons !a !(StrictList a) | Nil

strictMap :: (a -&gt; b) -&gt; StrictList a -&gt; StrictList b
strictMap _ Nil = Nil
strictMap f (Cons a list) =
  let !b = f a
      !list' = strictMap f list
   in b `seq` list' `seq` Cons b list'

strictEnum :: Int -&gt; Int -&gt; StrictList Int
strictEnum low high =
  go low
  where
    go !x
      | x == high = Cons x Nil
      | otherwise = Cons x (go $! x + 1)

double :: Int -&gt; Int
double !x = x * 2

list  :: Int -&gt; StrictList Int
list !x = Cons x (Cons x Nil)

foldlS = \f z l -&gt;
  case l of
    Nil -&gt; z
    Cons !x !xs -&gt; let !z' = z `f` x
                       in  z' `seq` foldlS f z' xs  

listlist :: StrictList (StrictList Int)
listlist = strictMap list $! strictEnum 1 100

myhead  :: StrictList a -&gt;  a
myhead =  \l -&gt;
  case l of
    Cons x xs -&gt; x

t :: Int
t = myhead (myhead listlist)

cartProdN12 :: StrictList (StrictList a) -&gt; StrictList (StrictList a)
cartProdN12 xss =
 foldlS h1 (Cons Nil Nil) xss where
  h1 !yss !xs = foldlS g Nil xs where
     g !zss !x = foldlS f zss yss where
       f !yss !xs = Cons (Cons x xs ) yss
         
r = cartProdN12 listlist
hr :: Int
hr =  myhead( myhead r)
```

依旧算不出来。

到此为止。 我已经没有思路了。由于是多层 foldl . 我的空间想象力无法得出结论。

* * *

我提了一个问题在 stackoverflow :

&lt;https://stackoverflow.com/questions/69863240/the-problem-of-foldl-with-multi-level-foldl-in-haskell#69863538&gt;

问题好像应该转换成： foldl 应该如何求出第一个结果 as fast as foldr.

再就是我的工具有问题： Haskell For Mac 算出第一个出来就不再算了 Lazy Lazy。给了我错误的诱导。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/37</guid><pubDate>Fri, 05 Nov 2021 14:28:07 +0000</pubDate></item><item><title>素数的埃拉托斯特尼筛法</title><link>https://www.jiamo.name/blog/blog/38</link><description>这个算法很容易理解，我用 python 的 yield 来实现，这样更优雅一些。

```python
def nats(n):
    yield n
    yield from nats(n + 1)

def sieve(s):
    n = next(s)
    yield n
    yield from sieve(i for i in s if i % n != 0)
```

首先可以问的是，前 n 个素数，可以这么简单得到：

```python
def first_primes(n):
    nat = nats(2)
    p = sieve(nat)
    return [next(p) for i in range(n)]
```

到这里还不能完，应该追问，n 以内的素数。 这里就有一个技巧，n 要提前结束，必须以某种方式告诉素数流。

```python
def le_nats(n, lt_n):
    if n &gt; lt_n:
        return
    yield n
    yield from le_nats(n + 1, lt_n)

def sieve(s):
    try:
        n = next(s)
        yield n
        yield from sieve(i for i in s if i % n != 0)
    except StopIteration:
        return

def primes_le(n):
    nat = le_nats(2, n)
    result = []
    for i in sieve(nat):
        result.append(i)
    return result
```

其实我刚开始是尝试在 sieve 里面做判断 n &gt; lt_n ，这是错误的。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/38</guid><pubDate>Mon, 24 Jul 2023 14:37:37 +0000</pubDate></item><item><title>二分搜索的上界和下界</title><link>https://www.jiamo.name/blog/blog/39</link><description>就个人喜爱而言，递归版的 二分搜索简洁直观

```python
def binary_search(data, target, low, high):
    if low &gt; high:
        return False
    else:
        mid = (low + high) // 2
        if target == data[mid]:
            return True
        elif target &lt; data[mid]:
            return binary_search(data, target, low, mid - 1)
        else:
            return binary_search(data, target, mid + 1, high)
```

但阅读《数学和范型编程》之后，如果中间元素有重复，需要知道第一个，或者最后一个的位置。先给出书本上的解法。

```python
def partition_points(a, n, f):
    r = 0
    while n:
        middle = r
        half = n // 2
        middle += half
        if not f(a[middle]):
            n = half
        else:
            middle = middle + 1
            r = middle
            n = n - half - 1
    return r

def lower_bound(data, target):
    return partition_points(data, len(data), lambda x: x &lt; target)

def upper_bound(data, target):
    return partition_points(data, len(data), lambda x: x &lt;= target)
```

那么递归版本，是否也存在等价版本呢？事实是存在的。binary_search 天生是 lower_bound 。

```python
def upper(data, target, low, high, ans=None):
    if low &gt; high:
        return ans
    else:
        mid = (low + high) // 2
        if target == data[mid]:
            ans = mid
            return upper(data, target, mid + 1, high, ans)
        elif target &lt; data[mid]:
            return upper(data, target, low, mid - 1, ans)
        else:
            return upper(data, target, mid + 1, high, ans)
```

返回的语义有点差别，返回的是重复的元素的最后一个有效 index 。书本上的 upper_bound 返回的是最后有效 index 的下一个位置。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/39</guid><pubDate>Tue, 25 Jul 2023 04:16:11 +0000</pubDate></item><item><title>集合划分的递归和迭代版本</title><link>https://www.jiamo.name/blog/blog/40</link><description>原来读书还是不仔细，《Combinatorial Generation》 里面的集合划分方法，被我直接忽略了，直到偶尔间看到 &lt;https://stackoverflow.com/questions/54025505/find-all-possible-solutions-of-stirling-number-of-second-kind-using-python/76832950#76832950&gt; 这个问题。 才回过头来去解决迭代版本。

递归版：

```python
def stirling3(a, n, k, f):
    if n == k:
        f(a)
    else:
        for i in range(0, k):
            a[n] = i
            stirling3(a, n - 1, k, f)  # this call String
            a[n] = n - 1
        if k &gt; 1:
            a[n] = k - 1
            stirling3(a, n - 1, k - 1, f)
            a[n] = n - 1
```

迭代版

```python
def stirling3_iterative(a, n, k, f):
    stack = [(n, k, a)]
    while stack:
        n, k, a = stack.pop()
        if n == k:
            f(a)
        else:
            for i in range(0, k):
                a[n] = i
                stack.append((n - 1, k, a[:]))
                a[n] = n - 1
            if k &gt; 1:
                a[n] = k - 1
                stack.append((n - 1, k - 1, a[:]))
                a[n] = n - 1
```

需要有调用的 wrapper 

```python
def helper(f, n=11, k=5):
    b = [i - 1 for i in range(n + 1)]
    r1 = 0

    def g(a):
        nonlocal r1
        r = defaultdict(list)
        for i in range(1, n + 1):
            r[a[i]].append(i)
        print(r)
        r1 += 1

    start = time.time()
    f(b, n, k, g)
    print("len= ", r1, time.time() - start)

if __name__ == "__main__":
    helper(stirling3, 4, 2)
    helper(stirling3_iterative, 4, 2)
```

为什么有 stirling3 因为还有其他不同的版本，但由于没有能转化成迭代算法，就不放出来了。</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/40</guid><pubDate>Fri, 04 Aug 2023 04:34:45 +0000</pubDate></item><item><title>python thread 并发和 trio 的异同</title><link>https://www.jiamo.name/blog/blog/41</link><description>生产水的一道题： &lt;https://leetcode.cn/problems/building-h2o/description/&gt;

&gt; _现在有两种线程，氧`oxygen` 和氢 `hydrogen`，你的目标是组织这两种线程来产生分子。_ _存在一个屏障（barrier）使得每个线程必须等候直到一个完整水分子能够被产生出来。_
&gt; 
&gt; _氢和氧线程会被分别给予`releaseHydrogen` 和 `releaseOxygen` 方法来允许它们突破屏障。_
&gt; 
&gt; _这些线程应该三三成组突破屏障并能立即组合产生一个水分子。_ _你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。_

使用原生 python thread 可以得到这样的代码：

```python
import threading

class H2O:
    def __init__(self):
        # 初始化信号量
        self.hydrogen_semaphore = threading.Semaphore(2)  # 两个氢
        self.oxygen_semaphore = threading.Semaphore(1)  # 一个氧
        self.barrier = threading.Barrier(3)  # 三个线程组成一个水分子

    def hydrogen(self, releaseHydrogen):
        # 获取氢的信号量
        self.hydrogen_semaphore.acquire()
        # 如果两个 h 已经过去，那么这里会阻塞第三个H
        # 等待直到一个完整的水分子能够被产生
        self.barrier.wait()  # 这里等待 H  和 0 直到另两个来到之后会过去
        releaseHydrogen()
        # 释放氢的信号量
        self.hydrogen_semaphore.release()

    def oxygen(self, releaseOxygen):
        # 获取氧的信号量
        self.oxygen_semaphore.acquire()
        # 等待直到一个完整的水分子能够被产生
        self.barrier.wait()
        releaseOxygen()
        # 释放氧的信号量
        self.oxygen_semaphore.release()

# 测试代码
def releaseHydrogen():
    print("H", end="")

def releaseOxygen():
    print("O", end="")

h2o = H2O()
water = "HHHHHHHHOOOHHHHHHHHHHOOOOOHHHHHHOHHOOOO"
threads = []

for molecule in water:
    if molecule == "H":
        threads.append(
            threading.Thread(
                target=h2o.hydrogen, args=(releaseHydrogen,)))
    else:
        threads.append(
            threading.Thread(
                target=h2o.oxygen, args=(releaseOxygen,)))

for thread in threads:
    thread.start()

for thread in threads:
    thread.join()
```

可以这么理解，粗浅的理解一种场景： 氢气线程可以立马放走两个，然后等等待在 barrer 上，

然后氧气线程得到一个之后，barrer 被突破。barrer 引入之后像是一种被动的并发。

就是一种感觉 barrer 在轮询一样，3个线程来了之后一起放过去。

trio 号称引入 structured concurrency 。 我们可以用一种主动的概念来做，相互通知。

```python
import trio

class H2O:
    def __init__(self):
        self.hydrogen_semaphore = trio.Semaphore(2)  # 两个氢
        self.oxygen_semaphore = trio.Semaphore(1)    # 一个氧
        self.hydrogen_event = trio.Event()
        self.oxygen_event = trio.Event()

    async def hydrogen(self, releaseHydrogen):
        async with self.hydrogen_semaphore:
            self.hydrogen_event.set()
            await self.oxygen_event.wait()
            releaseHydrogen()

    async def oxygen(self, releaseOxygen):
        async with self.oxygen_semaphore:
            await self.hydrogen_event.wait()
            releaseOxygen()
            self.oxygen_event.set()

# 测试代码
def releaseHydrogen():
    print("H", end="")

def releaseOxygen():
    print("O", end="")

async def main():
    h2o = H2O()
    water = "OOHHHHOHH"
    async with trio.open_nursery() as nursery:
        for molecule in water:
            if molecule == "H":
                nursery.start_soon(h2o.hydrogen, releaseHydrogen)
            else:
                nursery.start_soon(h2o.oxygen, releaseOxygen)

trio.run(main)
```

感觉这里 structured concurrency 并没有体现在业务逻辑，而是 open_nursery 之后不用主动 join 了。

hydrogen_semaphore 始终保持只有两个线程，然后氧线程会释放2两个。

最初的代码氢线程是这样的

```python
            self.hydrogen_count += 1
            if self.hydrogen_count == 2:
                self.hydrogen_event.set()
```

我觉的 hydrogen_semaphore 已经是 2 了，没有必要。

但 trio 版本固定输出 ohhohh 模式。**待研究，可以产生其他随机模式。**

* * *

一定有什么没有理解的地方， **releaseHydrogen 和 releaseOxygen 可以在各自的函数中随意放置，程序居然都是对的。**</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/41</guid><pubDate>Fri, 11 Aug 2023 08:57:30 +0000</pubDate></item><item><title>interpreter dispatch: function map vs reflect</title><link>https://www.jiamo.name/blog/blog/42</link><description>之前使用 golang 实现 的 rabbit language 的项目，其中一个 wvm 使用 stack vm 和 简略字节码设计， 实现方式使用 golang 的反射。 性能非常差，比 interpreter 还差。毕竟 interpreter 还要递归解析， 字节码是线性执行而已。

使用 reflect 的 大致代码：

```golang
func (vm *WVM) run(instructions []Instruction) {
	vm.pc = 0
	vm.running = true

	for vm.running {
		op := instructions[vm.pc].opcode
		args := instructions[vm.pc].args
		vm.pc++
		argValues := make([]reflect.Value, 1)
		method := reflect.ValueOf(vm).MethodByName(op)
		if args == nil {
			//argValues[0] = reflect.ValueOf(nil)
			method.Call([]reflect.Value{}) 
		} else {
			argValues[0] = reflect.ValueOf(args)
			method.Call(argValues)
		}

	}
}

func (vm *WVM) IPUSH(value int) {
	vm.istack = append(vm.istack, value)
}

func (vm *WVM) IPOP() int {
	index := len(vm.istack) - 1
	// Get the top element of the stack
	element := vm.istack[index]
	// Remove the top element of the stack
	vm.istack = vm.istack[:index]
	return element
}
```

所以去掉反射，使用 function map 。大致方式是：

```golang
func (vm *WVM) run(instructions []Instruction) {
	vm.pc = 0
	vm.running = true

	// Should update Instruction to Thread code
	opMap := vm.getOpcodeMap()

	for vm.running {
		op := instructions[vm.pc].opcode
		args := instructions[vm.pc].args
		vm.pc++

		if op == "LABEL" {
			continue
		}
		fn, exists := opMap[op]
		if !exists {
			panic(fmt.Sprintf("no such opcode %v", op))
		}
		fn(args)

	}
}

func (vm *WVM) IPUSH(value interface{}) interface{} {
	vm.istack = append(vm.istack, value.(int)) 
	return nil
}

func (vm *WVM) IPOP(value interface{}) interface{} {
	index := len(vm.istack) - 1
	// Get the top element of the stack
	element := vm.istack[index]
	// Remove the top element of the stack
	vm.istack = vm.istack[:index]
	return element
}

func (vm *WVM) FSUB(value interface{}) interface{} {
	right := (vm.FPOP(nil)).(float64)
	left := (vm.FPOP(nil)).(float64)
	vm.FPUSH(left - right)
	return nil
}

func (vm *WVM) getOpcodeMap() map[string]OpFunc {
	return map[string]OpFunc{
		"IPUSH":         vm.IPUSH,
		"IPOP":          vm.IPOP,
		"IDUP":          vm.IDUP,
		"FDUP":          vm.FDUP,
	}
}
```

由于放在一个 map， 函数签名必须一直，带来了 right := (vm.FPOP(nil)).(float64) 这种 ugly 的代码。没有想到什么好的解决方案。

但最终效果非常明显，性能上肉眼感觉有 10 倍提升。

具体 pr : &lt;https://github.com/jiamo/go-wabbit/commit/43d8fda115bb3611a9ab10549946acdcef111847&gt;</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/42</guid><pubDate>Mon, 21 Aug 2023 04:14:19 +0000</pubDate></item><item><title>尾递归优化</title><link>https://www.jiamo.name/blog/blog/43</link><description>为 &lt;https://github.com/jiamo/go-wabbit&gt; 添加尾递归优化。

llvm 的处理就是直接开启 "-O3" 的优化选项。

wasm 的处理稍微复杂一点，生成的 text webassembly (wat 文件) 中: 使用 return_call 和 替换 call.

关键就是什么时机替换，可以做很多复杂的提前计算，当 call 的时候，判定是不是 tail。

也可以不这么做，到 return 的时候，看最近的一个指令是不是 call 了同一函数。

具体见 pr : &lt;https://github.com/jiamo/go-wabbit/commit/978eaf488e2e7d5374f6a8dd58b85a19d0f154b2&gt;

wvm 的处理类似 wasm 添加一个 TAIL_CALL 指令

```golang
func (vm *WVM) TAIL_CALL(value interface{}) interface{} {
	label := value.(int)
	// vm.frame = &amp;Frame{vm.pc, make(map[int]interface{}), vm.frame}
	// we don't need save the frame, using the same one
	vm.pc = vm.labels[label]
	return nil
}
```

golang interpreter 好像无解？ 待研究</description><guid isPermaLink="false">https://www.jiamo.name/blog/blog/43</guid><pubDate>Wed, 23 Aug 2023 12:29:25 +0000</pubDate></item></channel></rss>