libbloom

概念

Bloom filter 是由 Howard Bloom 在 1970 年提出的二进制向量数据结构,具有很好的空间和时间效率,被用来检测一个元素是不是集合的成员。

Bloom filter 采用的是哈希函数的方法,将一个元素映射到一个 m 长度的阵列上的一个点,当这个点是 1 时,那么这个元素可能在集合内,反之则一定不在集合内。

libbloomBloom filter 的 C 语言实现库,其中哈希函数是 MurmurHash2。

特征

如果检测结果为是,该元素不一定在集合中;但如果检测结果为否,该元素一定不在集合中。

优缺点

  • 优点:插入和查询时间都是常数。它查询元素却不保存元素本身,节省大量的存储空间。当元素是密码时,不保存元素的特征使其具有良好的安全性。

  • 缺点:存在误报(false positive)。当插入的元素越多,错判“在集合内”的概率就越大。另外 Bloom filter 也不能删除一个元素,因为多个元素哈希的结果可能在 Bloom filter 结构中占用的是同一个位,如果删除了一个比特位,可能会影响多个元素的检测。

算法分析

以官方 test-basic 为例,简化的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
#include <assert.h>

#include "bloom.h"

int main()
{
struct bloom bloom;
assert(bloom_init(&bloom, 1002, 0.1) == 0);
assert(bloom.ready == 1);
bloom_print(&bloom);
bloom_free(&bloom);
}

输出为:

1
2
3
4
5
6
->entries = 1002
->error = 0.100000
->bits = 4802
->bits per elem = 4.792529
->bytes = 601
->hash functions = 4

数学原理参考:《Bloom Filter概念和原理

  • bytes 是最容易理解的,4802 位需要 601 字节存储。

  • bits = bits per elem * entries,每个元素需要多少位 * 元素个数。

  • hash functions = ceil(-ln(error) / ln(2))

  • bits per elem = -ln(error) / ln(2)^2

参考

https://baike.baidu.com/item/bloom filter

学习 Rust【3】所有权

摘要

Rust 三大设计宗旨:内存安全、零成本抽象、实用。本文从所有权角度来学习师兄妹的爱恨情仇“内存安全”。

所有权规则

  • 值归变量所有。

  • 当变量超出使用范围时,变量值所占用的内存将被释放。这是类似于 C++ 的 RAII 概念。

  • 变量值可以由其他变量使用,但需遵守由编译器强制要求的若干规则。

前两条是其它语言也有的,没啥好说,重点放在第三条。

四种使用方法和规则

  • 克隆(clone):此处将值复制到新的变量。新变量拥有新的复制值的所有权,而原始变量保留其原始值的所有权。

    你有一本书,稣按照你那本书,买了一样的书。你的书是你的书,稣的书是稣的书。

  • 移动(move):所有权被转移到另一个要使用该值的变量,原始变量不再拥有所有权。

    学姐含情脉脉地把她的书送给稣。

  • 不可变借用(immutable borrow):没有发生所有权转移,但是可以通过另一个变量读取该值。当借用变量超出范围,内存不会被回收,因为借用变量没有所有权。

    学长不太情愿地把书借给稣,并交代:“书借你,只能看,绝壁不要在上面做笔记,被我发现会砍死你的哦!我偶尔会找你查查。”

  • 可变借用(mutable borrow):可以通过另一个变量对该值进行读取和写入操作。当借用变量超出范围,内存也不会回收,因为借用变量没有所有权。

    稣把书借给学妹时说:“这书你随便用,把稣的书当做你自己的书,等到用不上时再还。”

重点在于一个“借”字:书的所有权属于其主人,但主人将书借出之后,自己是无法再在书上做笔记啦!

不可变借用(immutable borrow)规则

  • 借出期间,借方不能写:学长说过,在书上乱画,要砍死稣!

  • 借出期间,所有者不能写:这书学长已经学完,偶尔要复习,但已经不需要写笔记,借出去之后就更不会写了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
fn main() {
let mut owner = format!("学长的书");

// 学长大笔一挥,在书上记了一点心得!
owner.push('.');

// 稣不可变借用学长的书
let borrower = &owner;

// 学长随时可以来找稣翻翻他自己的书
println!("学长读{}", owner);
println!("学长再读{}", owner);

// 没问题,稣可以读学长的书
println!("稣读{}", borrower);

// error: 书还在稣手里,学长不能写
//owner.push('.');

println!("稣再读{}", borrower);

// error: 学长说过,在书上乱画,要砍死稣!
//borrower.push('.');

// 稣已经归还,学长可以做笔记了
owner.push('.');
}

可变借用(mutable borrow)规则

  • 不能多次可变借用:只能有一个独占的学妹(active borrow),稣不能同时承诺给多个学妹“随便用”,不然学妹们可能打起来……

  • 所有者不能再读写:书在学妹手里随便蹂躏,稣虽然心疼,但不能说!等她爽(huan)了再说吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
fn main() {
let mut owner = format!("稣的书");

// 学妹可变借用稣的书
let mutable_borrower = &mut owner;

// error[E0499]: cannot borrow `owner` as mutable more than once at a time
// 学姐也要借,稣表示:要书没有,要命一条!
//let mutable_borrower_b = &mut owner;

println!("学妹读{}", mutable_borrower);

// error[E0502]: cannot borrow `owner` as immutable because it is also borrowed as mutable
// 学妹暂时完全掌控稣的书
//println!("稣读不鸟{}", owner);

// 学妹大笔一挥,在书上记了一点心得!
mutable_borrower.push('.');
println!("学妹再读{}", mutable_borrower);

// 书已经归还给稣,稣可以做笔记了
owner.push('!');
println!("稣读{}", owner);
}

诗盗·隐玊

《#诗盗#·隐玊》:诗码中年初白头,科技人生稳如狗。神之八哥思千虑,一算钱袋拮九周。

注解

玊:通“士”。
稳如狗:稳妥地像狗,不是稳定。
神之八哥思千虑:要解决的问题是神级的难,脑力消耗很大。
一算钱袋拮九周:收入不行,只能过着拮据的生活。

改编自霹雳角色玉龙隐士诗号:

书剑青眼初白头,
智殊相悬问机难,
波澜困守役千虑,
一算龙隐决九川。

优化思维【5】合并步骤

前情

在《优化思维【3】消除没必要步骤》提到一个对象转化的例子:A 对象要转为 B 对象,实现时先把 A 对象转为中间对象 T,再将 T 转为 B 对象,由于两步都很容易实现,一个现有函数即可,所以很多人可能会采用这个思路。

下面要介绍的“合并步骤”,类似于优等生解应用题时“跳步”(一行合并多个步骤),可以作为前文的补充。

例子

1. 合并 cat 和 grep

以下命令 UMU 经常看到,其实它可以用 grep UMU test 来优化,减少一次管道交互。

1
cat test | grep UMU

2. 合并多个 sed

再看下面例子是从一个命令行里移除 A 和 C 两个选项:

1
2
3
cmd="EXE A=1 B=2 C=3 D=4"
removed=$(echo $cmd | sed -e 's/ A=[^ ]*//' | sed -e 's/ C=[^ ]*//')
echo $removed

其中两次 sed 可以合并为一次:

  • sed -e 's/ A=[^ ]*//;s/ C=[^ ]*//'

  • sed -e 's/ [\(A\)\(C\)]=[^ ]*//g'

  • sed -e 's/ \(A\|C\)=[^ ]*//g',这个 macOS 上不行。

  • sed -E -e 's/ (A|C)=[^ ]*//g',这个适合 macOS。

3. TFO

先查一下 sysctl net.ipv4.tcp_fastopen,一般应该是 1,说明客户端支持 TFO;如果是 2 则说明服务端支持;3 是同时支持。

TFO (TCP Fast Open) 是一种能够在 TCP 连接建立阶段传输数据的机制。使用这种机制可以将数据交互提前,降低应用层事务的延迟。

这其实也是一种合并步骤的思想,把传输数据合并到三次握手期间。

参考:

跟 UMU 一起玩 OpenWRT(入门篇16):Python3

为什么 Python3?

  1. Shell 不适合某些复杂运算,尤其是 OpenWRT 用的 ash。

  2. Lua 缺乏某些 SDK,比如说阿里云 SDK 就没有 Lua 版。

  3. Python2 已经过时。

  4. Node.js 在小型设备上不如 Python3 高效。

  5. C、C++ 之流太难了!Go、Rust 还得编译,麻烦。

  6. Perl、Ruby 已没落。

一个例子

当 IPv6 地址变化时,将地址发送到钉钉:https://github.com/UMU618/openwrt-ipv6-addresses

安装与调试

1. 安装可执行程序

1
opkg install python3-base

安装 python3-base 之后,就可以运行 python3 了。

1
2
3
4
5
6
7
root@UMU:~# python3
Could not find platform dependent libraries <exec_prefix>
Consider setting $PYTHONHOME to <prefix>[:<exec_prefix>]
Python 3.7.6 (default, Feb 11 2020, 12:41:31)
[GCC 7.5.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>>

用以下 Python 代码,打印目前已有的模块:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
import sys

## 打印的信息太长
i = 0
for m in sys.modules:
i += 1
print('{0:2d} {1:32s} {2}'.format(i, m, sys.modules[m]))

## 不精准
i = 0
for m in sys.modules.values():
i += 1
if m.__spec__:
v = m.__spec__.origin
elif m.__builtins__:
v = '-'
else:
v = ''
print('{0:2d} {1:32s} {2}'.format(i, m.__name__, v))

## 推荐使用
i = 0
for m in sys.modules.values():
i += 1
s = str(m)
start = s.find("'")
end = s.find("'", start+1)
k = s[start+1:end]
start = s.find('from', end+1)
if start > -1:
start += 4
end = s.find('>', start+1)
else:
start = s.find('(', end+1)
end = s.find(')', start+1)
v = s[start+1:end]
print('{0:2d} {1:28s} {2}'.format(i, k, v))

结果为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
 1 sys                          built-in
2 builtins built-in
3 _frozen_importlib frozen
4 _imp built-in
5 _thread built-in
6 _warnings built-in
7 _weakref built-in
8 zipimport built-in
9 _frozen_importlib_external frozen
10 io built-in
11 marshal built-in
12 posix built-in
13 encodings '/usr/lib/python3.7/encodings/__init__.pyc'
14 codecs '/usr/lib/python3.7/codecs.pyc'
15 _codecs built-in
16 encodings.aliases '/usr/lib/python3.7/encodings/aliases.pyc'
17 encodings.utf_8 '/usr/lib/python3.7/encodings/utf_8.pyc'
18 _signal built-in
19 __main__ built-in
20 encodings.latin_1 '/usr/lib/python3.7/encodings/latin_1.pyc'
21 io '/usr/lib/python3.7/io.pyc'
22 abc '/usr/lib/python3.7/abc.pyc'
23 _abc built-in
24 site '/usr/lib/python3.7/site.pyc'
25 os '/usr/lib/python3.7/os.pyc'
26 stat '/usr/lib/python3.7/stat.pyc'
27 _stat built-in
28 posixpath '/usr/lib/python3.7/posixpath.pyc'
29 genericpath '/usr/lib/python3.7/genericpath.pyc'
30 posixpath '/usr/lib/python3.7/posixpath.pyc'
31 _collections_abc '/usr/lib/python3.7/_collections_abc.pyc'
32 _sitebuiltins '/usr/lib/python3.7/_sitebuiltins.pyc'
33 atexit built-in

下面来实现获取 IPv6 地址的功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def net_hex_to_ipv6(h):
ipv6 = h[0:4]
i = 4
while i < len(h):
ipv6 += ':' + h[i:i+4]
i += 4
return ipv6

with open('/proc/net/if_inet6') as f:
for line in f:
p = line.split()
if p[3] == '00' and (int(p[4], 16) & 0x80) != 0x80:
ip = net_hex_to_ipv6(p[0])
print(ip)
f.close()

以上代码有个“美中不足”:只能打印地址的“首选格式”,不支持“压缩格式”。下面改进!

2. 安装轻量库

UMU 打算使用 socket 模块的工具函数格式化 IPv6 地址,但目前已安装的 python3-base 不带 socket 模块:

1
2
3
4
>>> import socket
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named 'socket'

所以需要安装 python3-light

1
opkg install python3-light

装完即可愉快地玩耍:

1
2
3
4
5
import socket

ip = '0618:0618:0618:0618:0000:0000:0000:0618'
print(ip, '->',
socket.inet_ntop(socket.AF_INET6, socket.inet_pton(socket.AF_INET6, ip)))

以上代码打印:0618:0618:0618:0618:0000:0000:0000:0618 -> 618:618:618:618::618

3. 全量安装

如果 python3-light 还不能满足您,推荐来个全家桶:

1
opkg install python3

PS: 不要以为只要上面这句就全装上了,前面的 opkg install python3-base 是必要的!如果只装 python3,则 /usr/bin/python3 并不存在!

(完)

Node.js 程序员的 C++ 进修指南【1】:SetTimeout

前言

  • 如果您看得懂,那么,这是 Node.js 程序员的 C++ 进修指南。

  • 如果您没看懂,那么,这是学 C++ 的劝退书!

目的

用 C++ 改写 Node.js 程序,主要目的可能有两个:保密、提高性能。

那么您肯定要问:为什么不用 Go 或者 Rust 改写?UMU 是推荐用 Go 或 Rust 的,而且相对改写为 C++ 要简单得多,本系列文章,可能从反面论证:您应该选择用 Go 或者 Rust 改写!

代码仓库

https://github.com/UMU618/cpp-for-nodejs-programmers

第一个例子

在《学习 Rust【2】减少代码嵌套》中,UMU 提到一个使代码平坦化的例子,咱们把其中最基本的功能提炼出来,成为最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
setTimeout(() => {
console.log('step1')
}, 1000)

setTimeout(() => {
console.log('step2')
}, 2000)

setTimeout(() => {
console.log('step3')
}, 3000)

这段代码实现的功能是:一秒后打印 step1,再一秒后打印 step2,再一秒后打印 step3,退出。

翻译为 C++

首先明确一点:JavaScript 是 JIT 语言,不用编译,语言宿主直接解释运行。C++ 是 AOT 语言,需要编译。所以我们需要编译器(比如 g++、clang++)和编译脚本(比如 make、cmake)。下面我们会选择在 macOS 上使用 clang++ 和 cmake 来编译 C++ 代码。其中,cmake 其实是用来产生 Makefile 的,如果您学过 Makefile,可以直接用它。

1. 安装依赖软件

  • 安装 Xcode,以获取 MacOSX.sdk。

  • 安装 clang++ 和 cmake:

1
2
brew install llvm
brew install cmake

2. STL 实现

  • C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
// set_timeout.cc
// UMU: 这是一个不太好的实现
#include <chrono>
#include <future>
#include <iostream>
#include <vector>

class Timer {
public:
template <typename T>
void setTimeout(T function, int delay);
void stop();
void wait();

private:
bool clear = false;
std::vector<std::thread> pool;
};

template <typename T>
void Timer::setTimeout(T function, int delay) {
this->clear = false;
pool.emplace_back(std::thread([=]() {
if (this->clear) {
return;
}
std::this_thread::sleep_for(std::chrono::milliseconds(delay));
if (this->clear) {
return;
}
function();
}));
}

void Timer::stop() {
this->clear = true;
}

void Timer::wait() {
for (std::thread& thread : pool) {
if (thread.joinable()) {
thread.join();
}
}
}

int main() {
Timer timer;
//timer.setTimeout([&]() { std::cout << "step1\n"; timer.stop(); }, 1000);
timer.setTimeout([]() { std::cout << "step1\n"; }, 1000);
timer.setTimeout([]() { std::cout << "step2\n"; }, 2000);
timer.setTimeout([]() { std::cout << "step3\n"; }, 3000);
timer.wait();
return 0;
}
  • cmake 脚本,CMakeLists.txt:
1
2
3
4
5
6
cmake_minimum_required (VERSION 3.5)
project (set_timeout)

add_executable(set_timeout set_timeout.cc)
set_property(TARGET set_timeout PROPERTY CXX_STANDARD 17)
target_compile_features(set_timeout PRIVATE cxx_auto_type)
  • 编译:
1
2
3
## cd to source code directory
cmake .
make

小结:以上代码,可用,但不推荐。首先它是用多线程模拟的定时器,当设置 N 个定时器时,将创建 N 个线程,这不够优雅。其次,当您取消定时器时,会发现它无法立刻取消并退出线程。

3. Boost Asio 实现

我们知道,Nodejs 内部使用 libuv 作为异步 IO 库,它是 C 实现的,用 C++ 调用 libuv 就显得不那么 C++,所以我们决定用和 libuv 同类且更强大的 Boost Asio 来代替。

  • 安装 boost,目前是 1.72.0 版:
1
brew install boost
  • C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#include <chrono>
#include <iostream>
#include <vector>

#include <boost/asio.hpp>
#include <boost/asio/steady_timer.hpp>

class Timer {
public:
template <typename T>
void setTimeout(T function, int delay);
void stop();
void wait();

private:
boost::asio::io_context io;
std::vector<boost::asio::steady_timer> timers;
size_t count;
};

template <typename T>
void Timer::setTimeout(T function, int delay) {
auto& timer = timers.emplace_back(boost::asio::steady_timer(io));
++count;
timer.expires_from_now(std::chrono::milliseconds(delay));
timer.async_wait([=](const boost::system::error_code& error) {
// boost::system::error::operation_canceled
// boost::asio::error::operation_aborted
if (!error) {
function();
}
});
}

void Timer::stop() {
for (auto& timer : timers) {
timer.cancel();
}
}

void Timer::wait() {
io.run();
}

int main() {
Timer timer;
//timer.setTimeout([&]() { std::cout << "step1\n"; timer.stop(); }, 1000);
timer.setTimeout([]() { std::cout << "step1\n"; }, 1000);
timer.setTimeout([]() { std::cout << "step2\n"; }, 2000);
timer.setTimeout([]() { std::cout << "step3\n"; }, 3000);
timer.wait();
return 0;
}
  • cmake 脚本,CMakeLists.txt:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
cmake_minimum_required (VERSION 3.5)
project (set_timeout)

add_executable(set_timeout set_timeout.cc)
set_property(TARGET set_timeout PROPERTY CXX_STANDARD 17)
target_compile_features(set_timeout PRIVATE cxx_auto_type)

set(Boost_USE_STATIC_LIBS ON)
set(Boost_USE_MULTITHREADED ON)
set(Boost_USE_STATIC_RUNTIME ON)
find_package(Boost 1.71.0)
if(Boost_FOUND)
include_directories(${Boost_INCLUDE_DIRS})
target_link_libraries(set_timeout ${Boost_LIBRARIES})
endif()

小结:好很多,但太难了……这真是劝退书!

八哥之神【番外篇8】

听说鲁豫要来采访稣
请戴口罩采访

1. 今年遇到史称的最长春节,稣都是怎么打发时间的?

改剧本。2019 年 8 月时,《八哥之神前传【1】》识界大战时就提到“心灵生化病毒”,后来要解释识界之外的识界是怎么毁灭的,人类为何移居太阴之背时,原剧本也是延续这个套路。但不幸的是作者所处的宇宙遭遇病毒疫情,为了不造成恐慌,急忙连夜做梦改剧本。目前倾向于 11.7 级地震。

这个跳跃有点快,我都跟不上了,这些不是还没写吗?

是还没写,但早已梦见。本剧的真正创作时间是作者大学时期,距今已经 15 年。

太震惊了!难怪剧中不少情节很明显不符合稣现在的年纪!

没错!大学时代就打的草稿,故事来源于自己的梦和听闻周边的故事。原本计划剧名:《一睡千秋》、《一梦千年》或者《魔脑在世三千年》的,但由于当时阅历不够,自觉写不好,所以写个故事发展路线就放弃。2018 年底无意间在老硬盘里翻出来,感慨良多,决定把“概要”升级成“初稿”。

为什么现在的剧名是《八哥之神》,是不是过于抽象?

没错!故意起这样奇葩的名字,主要是因为稣低调。另外,这个剧本里的中心思想太过玄幻,不适合一般人,当然不能起个易懂的名字。

稣在扩展剧本的时候,有什么比较深刻的体会呢?

想太多,写不出来!

“概要”只是一个青春言情剧,后来想写的是一个人的一生……但是一个人不够精彩,干脆把一堆人写成一个“稣”。本质上说“稣”其实是一群人,而不是一个。比如《八哥之神【4】》魔性山军犬养殖训练基地那个场景,其实就暗示男主角是个精分。

我糊涂了,是一个精分,还是一群?

有差别吗?

呃……问点别的!春节期间还有其它印象深刻的事情吗?

有的!一直在充电!

稣真是好学,放假也不忘充电!

哦弄!是给 MBP15 和手机充电!

好吧!真是尴尬。我注意到稣居然留长指甲!似乎不符合直男的人设!

没错!这是为了按公共电梯的按钮。您想,现在都 PHEIC 了,直接用手指接触?那面积好大的,多危险呀!

2. 有读者说《八哥之神前传》写得比《八哥之神》有条理,有逻辑,情节更科幻、更合理。

没错,这是事实。因为《八哥之神》其实是《八哥之神前传》的续集,一般续集都是变差的。稣很机智地采用倒叙,先写比较差的续集,这样别人就不会喷:“续集写烂了!”而是说“前传的角色居然更多,剧情更复杂!”

3. 还会出现哪些角色和剧情呢?能不能简单预告下。

出现的角色:李冰月、李心觎,一对双胞胎姐妹;孟长生,一个不婚主义的男子。

剧情:爷要被捅死了……这应该不算剧透,《八哥之神》已经明确这点,开场时圣小开就是一个 60 多岁的年轻人,和周易类似,但更惨一些,毕竟这是一部“探讨生命悲剧色彩”的言情科幻喜剧。

4. 您刚说过,想写一个人的一生,那么对您来说人生的意义是什么呢?

本剧的人生观:死亡并不长久,轮回再度孤独。请注意这里的“度”是个动词。

别慌!一个人的一生虽然意义不大,但放在无尽时间洪流里就有意义了,您可以以一辈子为单位进行迭代,这辈子尽量做得更好,让下辈子起点高一些。太上大道,我稣慈悲!

5. 太高深了,不是很能理解。能聊一下剧中情节和您现实生活的关系吗?

嗯……这些故事全都有现实原型!就是时间点改改,人名随便起一个,把几个现实故事重组。但是,这些故事没有一个是发生在作者本人身上的,作者只是擅长蹭热点,沾点人间俗气,以对抗挨踢无情。

6. 使您决定写本剧的动力是什么?必然有一个标志性事件吧!

上辈子死前,死神劝稣要放下,稣回答:不,稣要追求永恒!

然而稣还是死了,还是被洗脑洗得一点都不剩。为什么有些感觉转瞬即逝,再也回忆不起来?不行!稣要再体验一次,然后把它写下来。于是稣养成写日记的习惯。

有一天,稣发现如果按照中国人的平均寿命计算,稣已经过掉一半,回忆起自己的前半生,稣发现人生是孤独的最终奥义。每个阶段都会新朋友,也会失去一些旧朋友。就是上一次转变的时机来临前,稣突然领悟到一个真谛,我们无法选择不失去,但可以选择阶段性先失去的是什么!比如哪类朋友应该舍去——那些无法互相理解的。

接着稣又发现,理解不是一件容易的事情,互相理解这个要求有点高。只要能合作就行,应该舍去那些无法合作的。最终稣终于理解人间的一个催化剂:嫉妒。于是稣感觉人间的故事学习得差不多了,可以总结一下,于是开始写剧本。

7. 还会写其它故事吗?

身为天族,不能过度体验和干预人间。写代码比较正经。

8. 最后一个问题:传说稣身家千万?

个币。

什么?怎么骂人呢?

真的。

八哥之神前传【8】

2042 年,床上

古思:怎么……这么快?爷不是要敲代码么?

圣小开:一日之计在于晨,美好的早晨当然是睡回笼眯最好了!说不定还能再见到狐狸精。

古思:那我先去洗澡!

圣小开:大早上的洗啥澡,又没让你侍寝。

古思:哦,我也换睡衣陪爷躺躺。

圣小开:好多年没和别人睡眯眯了,有点不习惯。

古思:那我们聊天吧!床除了睡觉,另一个大用途就是聊天。

圣小开眼神一撇:你会聊啥?C 还是 C++?

古思:C++!而且我真学过,贾老师特地要求我学的。

圣小开:反应真快。爷还是亲手确认一下……

古思:爷的手好冰呀!

圣小开:嗯,爷还是稣的时候体温就低于常人,当爷后体温就更低了,需要一个暖手宝。

古思:这点我看过的资料并没有提到。

圣小开:贾总是不是和你说了爷很多八卦?

古思:是有一些基本介绍,也没啥特别八卦的!我还得多直接从爷处了解。

圣小开:有啥问题就问吧,如果问得不好爷可能就眯过去了。

古思:爷刚才提到好几次“眯”,为什么睡觉要说成睡眯眯呢?这种骚里嗲气叫法不符合爷的人设!

圣小开:骚里嗲气?眯眯——其实是爷的绯闻女友!

古思:哇哦……原来如此,看来田心挖到第一个八卦咯。

区块链上的故事

9102 年的春天,一个风雨交加的夜晚,稣和王博士聊完一个关于人工智能的天后,精神还有点紧崩,边低头走路边思考科技的变幻和人生的虚幻。

突然偶遇现在被人称为稣的绯闻前女友的月光女神卢眯眯。距离上次见面过去快一年,相逢自是有缘,人生到底是虚幻,还是真实,尽在研究自己和参考他人,何不乘机聊聊人生?然而附近像样的小资消遣场所都已打烊,也没地方坐,她提议去她宿舍。稣心想那是员工宿舍,应该很安全,稣不可能被人暗杀,如果被仙人跳,稣只要大喊一声,这破地方认识稣的少说也有 60 个!于是果断去了。

进屋后温暖许多,在温差作用下,居然一时犯困。她去洗澡的时候,稣不小心就在她床上睡着。

醒来已经快凌晨五点。她就躺在身边。稣想:楼下宿舍门没有门禁卡是出不去的,又不忍心叫醒她,于是一个果断的决策:睡眯眯。

这一觉睡得很好,是稣一生中少数几个质量极佳还能记得的觉之一,于是稣从此改口称睡觉为睡眯眯。

唯一美中不足的是……

2042 年,床上

古思:吓醒了?

圣小开:不是……稣偷偷摸摸摸了一下,眯眯的咪咪原来只比六舅大一点点!

古思:六舅?

圣小开:嗯,是爷的舅舅,他很六,所以叫他六舅。不说他,总之就是比你小挺多的……

古思:原来爷喜欢这个!田心还可以去加大一些。

圣小开:不用,不用。适可而止!适可而止!爷只是用理性的眼光衡量!

古思:爷到底是正经还是不正经呢?好难分辨!

圣小开:一名男性整形医生整天研究女人的胸,你说他正不正经?

古思:这没有不正经呀!

圣小开:一个男孩整天研究女人的胸,因为他立志成为一名整形医生,你说他正不正经?

古思:好像有不正经的味道。

圣小开:嗯哼!区别在于专不专业!如果一个人业余研究异性心理,多半会被判定为不正经,而一名专业的心理学家,怎么研究异性心理,都是正经的。

古思:爷想说什么?

圣小开:只有假不正经,才能知道谁是假正经!这是一种调试八哥人生的方法。

古思:听起来,其实爷是很正经的人,故意表现得有些不正经?

圣小开:是的,正经得无趣,无趣得没朋友。爷还是要假装有朋友的。

古思:调试八哥人生是什么意思?

圣小开:很多时候,很多人,是不会说实话的,需要一些研究手段。比如陪他们演戏,但又不能表现得不自然,所以爷研究过这本《悲剧演员的自我修养》。

古思:贾老师说爷的好奇心很重,原来如此呐!

圣小开:不!这是大部分人对爷的误解。这个误解好大,大到爷每次解释都会被当成蛇精病。

古思:em?怎么回事?

圣小开:爷是质疑,不是好奇。别人是什么样的,爷都可以接受!爷从这个世界的基础开始质疑,以致于对别人的存在和真伪也质疑。比如,偶尔发现别人好像喜欢自己,就会去研究她是不是真实存在的,是不是有主观意志,是不是真的喜欢,以及为什么她会喜欢爷。反过来,爷喜欢别人也一样各种质疑。

古思:这是不是多疑?不自信?

圣小开:肯定是多疑。爷连自己都质疑,有时甚至怀疑“我思故我在”也有八哥!是在思考,但在哪里思考呢?在自己脑里,还是在远方的某处?不自信有点,但也不他信呀!

古思:田心喜欢爷是基因和洗脑程序决定的,是真实的。

圣小开:哦?你不抗拒一下这种安排吗?

古思:贾老师说,爷要是死了,我会进入重置状态,记忆可能全部丢失,和死掉无异。

圣小开:那爷睡眯眯后,你会怎么样?

古思:没有特别设定。可以一起睡呗。【心想:以前是不敢抗拒,现在看来没必要抗拒。】

圣小开心想:在爷的床上,没人可以比爷晚睡!等你丫睡着后,爷就用各种反侦察仪器检查你。

死神的套路

前情

每年春节期间,稣都要研究玄学,今年岂能例外?

一神论

每个宇宙都有且只有一个神,就是您信的那个。祂有不同的名字,比如信基督,那神的名字是耶和华;道家神叫太上老君。

其实神就那么一个,不同的人给祂取不同的名。也可以是您自己,总之您信就行。

稣信死神。

死亡并非永恒

没有什么状态是永恒的。死亡也不是。宇宙能创造无穷的生命,总有一天可以重现死去的生命。就像您随便写个数字序列,在 pi 里一定能找到无数次。

记忆是暂时的,意识比记忆长久。生死轮回,可没保证在地球轮回。众生平等的理论来源于此,这辈子在地球上当人快活,下辈子可能在火星当虫吃土。

天地不仁以圣人为刍狗

人喜欢强人,便崇拜他们。死神也会喜欢强人,但奴役他们。很多强人都被死神叫去服务祂了——这就是死神的套路。

什么是圣人?圣人一定是强大但又克制自己的强大的,所以他们没有崇拜这种情感,他们只敬畏天地。

您说自己有一技之长,但那是什么造就的?基因、营养、后天练习、时代巧合等,圣人认为这些没啥,不值得欣赏。

死神要人命时,很克制,不轻易收走。——这才是值得欣赏的。

反面,不知克制食欲暴饮暴食、纵欲过度,身体会很快坏掉。

鲁信说过:稣劝您当扫地僧和鸠摩智,而不是乔峰。

死神之下皆为蝼蚁

死神眼里正邪无异,善恶不分。但人们如果自己规定了什么叫善恶,还自以为是善,当他们发现自己原来是恶时,就会遭到自己的诅咒。

影剧里,恶人洗白或善人作恶往往命不久矣。恶人可能想赎罪救人而死,善人则可能是信仰崩塌,生无可恋。总之都是后悔死的。

善良真的是一种有能力才能选择的修炼模式,不是自我认定和按条款划分这么简单。法律应该高于道德,因为很多“道德”条款,根本上是错误的。

PS: 死神根本不关心您善不善良,对凡人来说,量力而行才是科学的。

顺天而行

一个人只要坚持追随天道,时间长了就会很牛逼。天道并非就是美好的,凡人只能追随那些追随天道的圣人,比如老子。

前面已经提到“量力而行”这个词,凡人不可能做到顺天而行,量力而行就好。

新型冠状病毒

态度

战略上藐视敌人,战术上重视敌人。

经验

世界上确实有两种人:年轻人和中老年人。

  • 年轻人对 SARS 可能没什么印象。即使现在被感染新型冠状病毒,靠自身免疫力战胜病毒的概率也比中老年高很多。稣发现他们中一部分人,对疫情并不关心,甚至认为别人讨论疫情是在制造恐慌……稣认为如果发现有人造谣、信谣、传谣,辟谣打脸就行,但干预别人正常讨论疫情的自由就不太好了。毕竟年轻人迟早也要变成中老年人。

  • 中老年人很可能本来就有一些慢性病,要是被感染,死亡率就比较高。

稣属于经历过 SARS 封校的中老年人。当年还被认为是疑似病患,被请去医院体检,还好没事。所以稣是属于相当重视的这类。

贪生怕死

小时候经常会看到一些武侠剧,某个配角嘴里冒出一句:“没想到你居然是贪生怕死之辈!”语气带着不屑和鄙视!但说完,一般他就挂了……每每看到类似情节,稣的内心都是一阵纳闷:“贪生怕死不是正常的吗?这个人为了点莫名其妙的小事就去死,好像才不正常吧?”

有人说这次新型冠状病毒死亡率不高,没啥好操心的。

稣认为死亡率可能真不高,但却是多出来的一种。我们本来也存在被流星砸死的可能,但加上“新型冠状病毒致死”这一种可能性之后,整体死亡率明显是提高了,干嘛冒这个风险?

三思而后行

造谣者一般都是为了利益或者某种心理刺激。作为小老百姓,造谣应该不多,但信谣传谣就多了去。有些人为显示自己信息渠道多而广,就喜欢第一时间转发信息,而不是先鉴别。这种心理很多人都有。有些人不在网络上传谣,但聚会当面传是一样一样的。

也有些人是没鉴别能力,宁可信其有。稣在学习的路上,也没少被谣言短暂攻破过,要三思而后行和及时修正。

对自然心存敬畏

吃野味风险很高,那些爱吃的人估计也有一种攀比、装逼的心理,因为吃起来危险反而去吃。这是病,得电!

RNA 病毒因为是单链不稳定,所以变异能力比较大,很难预测后期是毒性减弱还是增强,以及人群里会不会出现超级传播者。流感病毒也是 RNA,稣基本每一两年都中一次,上一次的抗病毒经验无效,说明中的很可能是新变异品种。

相信科学,但也要明确还有很多病现在的医学治不了。敬畏自然,主动预防被感染!