C++: Variadic Template and Fold Expression are Powerful.

这两天做一个Pipeline的Framework,见识了现在C++ Generic Programming 的强大功能,比原来的 Object Oriented Programming 看上去 Cool 多了:完全没有关系的一堆类型,一样可以放在框架里操作,写出来的程序也没有任何效率的损失,看来这些年C++又进步了很多。下面简要说说我的设计。

Class BaseData 表示 Pipeline 中的一个数据节点。 Class Phase 表示针对数据的某个操作。 Phase 接受一些 BaseData 作为输入,并输出到另外一些BaseData。为了灵活和简单起见,Phase 不设任何 Virtual Method 作为接口,只要就实现一个 Run Method 即可。程序概要如下:

class BaseData {
public:
 get() ...
 set() ...
};

class DataA : public BaseData {...};
class DataB : public BaseData {...};
class DataC : public BaseData {...};
class DataD : public BaseData {...};

class Phase {};

class PhaseA : public Phase {
  void Run(DataA*, DataB*, DataC*) {...}
};
class PhaseB : public Phase {
void Run(DataC*, DataD*) {...}
};

class Pipeline {
private:
  DataA a_;
  DataB b_;
  DataC c_;
  DataD d_;

public:
  Run() {
    AddPhase(new PhaseA(), &a_, &b_, &c);
    AddPhase(new PhaseB(), &c_, &d_);
  }

  void AddData(BaseData* base_data) {
     ...
  }

  // This is variadic template.
  template<typename PhaseType, typename... Types>
  AddPhase(PhaseType* phase, Types args...) {

    // This is fold expression, avoid template recursion.
    (..., AddData(args));

    // This is variadic forward.
    std::function<void()> run = 
        std::bind(&PhaseType::Run, phase, args...);
  }
};

程序的精妙全在最后的AddPhase 这个函数,它首先通过一个 Variadic Template 来处理任意接口的函数,然后又通过一个 Fold Expression 对所有的数据进行了类型检查和处理,最后又使用 variadic forward 创建了一个 Closure 供以后调用,真是妙不可言啊。

在做了若干后续改进之后,今天终于在Team Meeting 上把这个东西讲出去了,心头考虑了大半年的东西终于一朝做出,真是很开心。 🙂

Use Mouse in Terminal Vim like a modern editor

现代的编辑器(Ultraeditor, Editplus) 对于鼠标的使用非常频繁,最常见的是使用鼠标单击来移动光标,鼠标滚轮来上下翻页,或者鼠标双击选中某个词,然后拷贝或者粘贴替换。我最近学到了如何在Terminal-Based Vim 中也这样操作。

首先设置Vim 在 Normal Mode 和 Visual Mode 下激活鼠标:

set mouse=nv

这样我们就可以用鼠标单击或者滚轮移动光标了。另外,鼠标双击选中单词也正常工作了。

值得一提的是,我们也可以用键盘来选中一个词:假定我们的光标停在 information 这个词上(任何位置都可以),按下

viw

就可以选中整个词,这个操作不需要鼠标,在某些情况下很方便。

接下来的事情就容易了,拷贝:

y

粘贴并替换当前选中的词:

p

 

编译 Vim (Compile Vim Wiht Python 3 Support on Debian)

下载源文件

$ git clone https://github.com/vim/vim.git
$ cd vim/src
$ vi Makefile

编辑Makefile

  • 选择编译器
# COMPILER - Name of the compiler {{{1
# The default from configure will mostly be fine, no need to change this, just
# an example. If a compiler is defined here, configure will use it rather than
# probing for one. It is dangerous to change this after configure was run.
# Make will use your choice then -- but beware: Many things may change with
# another compiler. It is wise to run 'make reconfig' to start all over
# again.
#CC = cc

# For some reason, gcc does not install itself as /usr/bin/gcc
CC = gcc-9
#CC = clang

if gcc is not installed, installing it by running

$ sudo apt update
$ sudo apt install build-essential
  • 关闭图形
# GUI - For creating Vim with GUI (gvim) (B)
# Uncomment this line when you don't want to get the GUI version, although you
# have GTK, Motif and/or Athena. Also use --without-x if you don't want X11
# at all.
CONF_OPT_GUI = --disable-gui

# X WINDOWS DISABLE - For creating a plain Vim without any X11 related fancies
# (otherwise Vim configure will try to include xterm titlebar access)
# Also disable the GUI above, otherwise it will be included anyway.
# When both GUI and X11 have been disabled this may save about 15% of the
# code and make Vim startup quicker.
CONF_OPT_X = --without-x
  • 支持  Python3

安装 Python3 库文件

$ sudo apt install python3.8 python3.8-dev python3-distutils

静态链接 Python 解释器。

# PYTHON
# Uncomment lines here when you want to include the Python interface.
# This requires at least "normal" features, "tiny" and "small" don't work.
# NOTE: This may cause threading to be enabled, which has side effects (such
# as using different libraries and debugging becomes more difficult).
# For Python3 support make a symbolic link in /usr/local/bin:
# ln -s python3 python3.1
# If both python2.x and python3.x are enabled then the linking will be via
# dlopen(), dlsym(), dlclose(), i.e. pythonX.Y.so must be available
# However, this may still cause problems, such as "import termios" failing.
# Build two separate versions of Vim in that case.
#CONF_OPT_PYTHON = --enable-pythoninterp
#CONF_OPT_PYTHON = --enable-pythoninterp --with-python-command=python2.7
#CONF_OPT_PYTHON = --enable-pythoninterp=dynamic
#CONF_OPT_PYTHON3 = --enable-python3interp
#CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.8
#CONF_OPT_PYTHON3 = --enable-python3interp=dynamic

CONF_OPT_PYTHON3 = --enable-python3interp --with-python3-command=python3.8 --with-python3-config-dir=/usr/lib/python3.8/config-3.8-x86_64-linux-gnu

编译和安装

$ make

$ sudo make install

清理一切,从头开始

$ make distclean

 

第一代之后,基因型不再变化(Stationary GenoType Distribution)

基因 A 和 a是一对等位基因,在人群中构成了三种基因型:AA, Aa 和 aa,其中A是显性,a是隐性。在某些遗传性状比如蓝眼睛、左撇子等,基因型决定了实际表现的性状,假定A代表右撇子,a代表左撇子,那么Aa和AA都表现为右撇子,只有aa表现为左撇子。

假定这三种基因型在男性和女性中的分布概率是一样的,分别是 u, 2v, w,那么我们有:

(1)   \begin{equation*} \begin{split} P(AA) = u \\ P(Aa) = 2v \\ P(aa) = w \\ u + 2v + w = 1 \end{split} \end{equation*}

如果我们用p代表基因A在人群中的概率,我们有:

(2)   \begin{equation*} P(A) = p = u + v \end{equation*}

同样,我们用q代表基因a在人群中的概率,我们同样有:

(3)   \begin{equation*} P(a) = q = w + v \end{equation*}

因为我们假定男性和女性的基因型分布是一样的,那么对于下一代子女来说,因为他(她)们的基因一半来自父亲,另一半来自母亲,所以他们之中纯合基因型AA发生的概率就是继承自父亲和母亲的基因都是A的概率相乘,也就是:

(4)   \begin{equation*} u_1 = p^2 = {(u+v)}^2 \end{equation*}

根据同样的道理,子女代中基因型Aa和aa发生的概率分别是:

(5)   \begin{equation*} \begin{split} 2v_1 = 2pq = 2 (u+v)(w+v) \\ w_1 = q^2 = {(w+v)}^2 \end{split} \end{equation*}

上面的公式中我们依然使用 u, v, w 代表 基因型AA,Aa和 aa 的概率,但是使用下标1来表示这是子女代。有了每种基因型的概率之后,我们可以知道子女代中实际基因A和a的分布概率是:

(6)   \begin{equation*} \begin{split} p_1 = u_1 + v_1 \\ q_1 = w_1 + v_1 \end{split} \end{equation*}

那么再下一代的三种基因型的概率分别是多少呢,同样我们可以简单计算如下:

(7)   \begin{equation*} u_2 = p_1^2 = {(u_1 + v_1)}^2 = u_1^2 + 2u_1v_1 + v_1^2 \end{equation*}

(8)   \begin{equation*} \begin{split} &2v_2 = 2 p_1 q_1 \\ &= 2(u_1 + v_1)(w_1 + v_1) \\ &= 2u_1w_1 + 2 u_1v_1 + 2v_1 w_1 + 2v_1^2 \end{split} \end{equation*}

(9)   \begin{equation*} w_2 = q_1^2 = {(w_1 + v_1)}^2 = w_1^2 + 2w_1v_1 + v_1^2 \end{equation*}

同样,第二代中基因A和a的概率分别是:

(10)   \begin{equation*} \begin{split} p_2 &= u_2 + v_2 \\ &= (u_1^2 + 2u_1v_1 + v_1^2) + \\ &\quad (u_1w_1 +  u_1v_1 + v_1 w_1 + v_1^2) \end{split} \end{equation*}

(11)   \begin{equation*} \begin{split} q_2 &= w_2 + v_2 \\ &= (w_1^2 + 2w_1v_1 + v_1^2) + \\ &\quad (u_1w_1 +  u_1v_1 + v_1 w_1 + v_1^2) \end{split} \end{equation*}

计算了这么拉拉杂杂一大堆,到底有什么用处呢?下面是关键的一步,因为我们有:

(12)   \begin{equation*} p + q = u + 2v + w = 1 \end{equation*}

显而易见:

(13)   \begin{equation*} q = 1 - p \end{equation*}

我们很容易推导出上一代的基因A的概率和下一代基因A的概率关系如下:

(14)   \begin{equation*} p_1 = u_1 + v_1 = p^2 + pq = p^2 + p(1-p) = p \end{equation*}

同样的道理,我们也可以看到:

(15)   \begin{equation*} \begin{split} q_1 = q \\ p_2 = p_1 \\ q_2 = q_1 \\ p_3 = p_2 \\ q_3 = q_2 \\ ... \end{split} \end{equation*}

上式说明,在理想的情况下(基因A和a在男女中比例一致,每一个人都有同样的机会生育下一代,下一代的生男孩女孩的概率一致),每一代中基因A和a的分布实际上没有任何变化,这也符合我们关于遗传的直觉。但是基因型AA,Aa和aa的概率就不是这样了。一般情况下:

(16)   \begin{equation*} \begin{split} u_1 \ne u \\ v_1 \ne v  \\ w_1 \ne w \end{split} \end{equation*}

但是

(17)   \begin{equation*} \begin{split} u_1 = u_2 \\ v_1 = v_2  \\ w_1 = w_2 \\ u_2 = u_3 \\ v_2= v_3  \\ w_2 = w_3 \\ ... \end{split} \end{equation*}

这说明实际的基因型在第一代之后就稳定下来,不再变化。回到我们前面左撇子的例子:在理想情况下,第一代和第二代的左撇子比例(基因型,u,v,w)可能不一样,但是他们中的左撇子基因(p,q)实际是一致的,从第二代以后,基因型的比例也稳定下来,不再变化。