01背包~四次元背包里都会装上什么呢~\(≧▽≦)/

啊w 01背包呐~给出一堆不可拆分的东西,它们有各自的重量和对你来说相应的价值,但是你的背包能装的最大重量是有限的,这个时候要如何选择装哪些东西使得背包里的东西有着最大的总价值呢~

贪心算法

第一种想法则是分别计算出每样东西的单位重量的价值,然后按照降序排序,最后在背包能放的前提下,取单位重量的价值最大的那一件放进去即可w

这种想法则是贪心算法,但是遗憾的是,贪心算法在每件物品不可分的条件下,它不能总是保证背包里的东西有着最大的总价值。例如物品的情况如下表

物品i 重量 价值 单位重量的价值
1 10 60 6
2 20 100 5
3 30 120 4

而背包最大能装下50重量,在这种情况下,按照贪心算法的话,我们会拿A和B,也就是总价值为160~

但是我们稍稍计算一下就知道实际上选择B和C才能使背包里的东西有着最大的总价值,也就是220 www

使用贪心算法求解并不难~按照上面的思路来写就好

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

value  = [60, 100, 120]
weight = [10, 20, 30]
capacity = 50

def greedy(value, weight, capacity):
    """
    0/1 背包 贪心
    """
    import numpy as np
    # 计算每件物品单位重量的价值
    # 为了方便 我们把该物品原始的下标也放进其对应的结果里
    ratio = [[i, value[i]/weight[i]] for i in range(len(value))]
    # 按照单位重量的价值降序排序
    ratio = sorted(ratio, key=lambda x: x[1], reverse=True)
    # 一开始的总价值为 0
    value = 0
    # 一开始都没有放进背包
    choose = np.zeros(len(value))
    for i in range(len(value)):
        # 获取排序之后 某物体的原始下标
        index = int(ratio[i][0])
        # 如果背包能装下该物品
        if capacity - weight[index] >= 0:
            # 装进去~
            # 容量减少
            capacity -= weight[index]
            # 价值增加
            value += value[index]
            # 标记该物品被装进了背包
            choose[index] = 1
    # 返回最后的总价值、有哪些物品被装进去
    return (value, choose)

print(greedy(value, weight, capacity))

穷举算法

那么贪心既然不好用的话,我们就多试试各种组合?也就是穷举所有的可能性w

其实题目的话——01背包——也在暗示我们可以用二进制来表示对这些物品的取舍情况,有 n 个物品的话,就用 n 个 bit 的二进制位就好~

蓝后既然又是二进制的话,每次给那个 n 个 bit 的二进制数 +1 就可以对应一种情况了~随后按照二进制位的 0/1 来判断,找出最大的那一种赋值就好www~

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

value  = [60, 100, 120]
weight = [10, 20, 30]
capacity = 50

def bruteforce(value, weight, capacity):
    """
    0/1背包 暴力搜索
    """
    
    # 最大的价值
    maxv = -1
    # 最大价值所对应的那组选择
    max_group = -1
    
    # 从 0b0 到 0b11..11 (共 len(value) 个 1)
    for i in range(2**len(value)):
        # 当前选择的容量
        current_w = 0
        # 当前选择的价值
        current_v = 0
        # 对应到二进制上
        for b in range(len(value)):
            if (i >> b) & 1:
                # 如果还能装下
                if current_v + value[b] <= capacity:
                    # 继续计算
                    current_w += weight[b]
                    current_v += value[b]
                else:
                    # 否则这组之后的都不用算了
                    break
        # 如果这组的价值比之前的大
        if current_v > maxv:
            # 更新
            maxv = current_v
            max_group = i
    # 返回最大价值和选择
    return (maxv, bin(max_group)[2:])

print(bruteforce(value, weight, capacity))

虽然穷举法可以保证我们找到最优解,但是其复杂度是$O(2^n)$的,物品数量每增加1,计算量就要翻倍∑(゚Д゚)

动态规划

那。。。聪明一点的方法有么~是有的ω

动态规划(Dynamic Programming)的方法就可以做到~

#!/usr/bin/env python3
# -*- coding:utf-8 -*-

value  = [60, 100, 120]
weight = [10, 20, 30]
capacity = 50

def dp(value, weight, capacity):
    """
    0/1背包 动态规划
    """
    import numpy as np
    # 初始化窝们的memo~
    memo = np.zeros((len(value) + 1, capacity + 1))

    # 开始填表w
    # 首先 i 是迭代每一件物品~
    for i in range(0, len(value)):
        # 蓝后是每一种可能的背包剩余重量
        # 这里简化代码,从 [0, capacity] 的每一个数都考虑了
        # 要优化的话,可以从这里下手~
        for j in range(0, capacity + 1):
            # 在不选择第 i 件物品时,无论剩余承重 j 是多少
            # 背包的装的物品的总价值都不会变
            memo[i][j] = memo[i - 1][j]
            
            # 如果剩余容量 j 可以让窝们考虑选不选的话
            if j >= weight[i]:
                # 那么跟窝们之前说的一样
                memo[i][j - weight[i]] = max([memo[i-1][j]+ value[i], memo[i - 1][j - weight[i]]])

    # 最后返回在有 n 件物品时,剩余容量为 0 时的值就可以了
    return memo[len(value) - 1][0]

print(dp(value, weight, capacity))

回溯法

除了动态规划之外,我们还有回溯法~和解决N皇后问题的时候类似~

#include <iostream>
#include <map>
#include <vector>

using namespace std;

/*
输入~
第一行是指有 n 件物品
接下来从第二行起、连续 n 行是这些物品
 第一个数是 weight,第二个数是 value
整个输入的最后一行是背包最大可以装的重量w
5
2 6
2 3
6 5
5 4
4 6
10
 */

struct object {
    int weight;
    int value;
};

/**
 * 01背包w
 *
 * @param capacity       当前背包还能装多少
 * @param value          当前背包里东西的总价值
 * @param i              当前考虑的是下标为 i 的物品是否要放进去
 * @param objects        所有的物品
 * @param take_it_or_not 记录第 i 件物品是否要当进去
 */
pair<int, vector<bool>> backpack(int capacity, int value, int i, const vector<object *> &objects, vector<bool> take_it_or_not) {
    // 我们有米有下标为 i 的物品
    if (i >= objects.size()) {
        // 如果没有就返回当前价值
        return pair<int, vector<bool>>{value, take_it_or_not};
    }
    
    // 有的话,就拿出下标为 i 的物品
    object * current_obj = objects[i];
    
    // 不管背包还能不能装
    // 对于这一件物品,我们都*可以*选择不去装它
    pair<int, vector<bool>> not_take_i = backpack(capacity, value, i + 1, objects, take_it_or_not);
    
    // 如果装不下这件物品的话
    if (current_obj->weight > capacity) {
        // 我们就直接返回不去装它的结果w
        return not_take_i;
    }
    
    // 如果还可以装下这件物品的话
    // 我们就继续看看变化之后(容量减少、价值增加)背包的情况
    pair<int, vector<bool>> take_i = backpack(capacity - current_obj->weight, value + current_obj->value, i + 1, objects, take_it_or_not);
    
    // 比较拿/不拿着一件物品
    if (not_take_i < take_i) {
        // 拿上它的价值更多
        // 那就记下~
        take_i.second[i] = true;
        return take_i;
    } else {
        // 否则就不拿这一件w
        not_take_i.second[i] = false;
        return not_take_i;
    }
}

int main(int argc, const char * argv[]) {
    int n;
    cin >> n;
    
    vector<object *> objects;
    // 依次输入 n 件物品
    while (n--) {
        object * obj = new object();
        // 第一个数是 weight,第二个数是 value
        cin >> obj->weight >> obj-> value;
        
        // 放进数组里~
        objects.push_back(obj);
    }
    
    // 记录某一件物品是否要装进背包
    vector<bool> take_it_or_not;
    // 预先调整好大小
    take_it_or_not.resize(objects.size());
    
    // 输入背包的最大重量
    int capacity;
    cin >> capacity;
    
    // 开始尝试吧w
    pair<int, vector<bool>> maximum = backpack(capacity, 0, 0, objects, take_it_or_not);
    printf("maximum: %d\n", maximum.first);
    for (int i = 0; i < maximum.second.size(); i++) {
        printf("%d: %s\n", i, maximum.second[i] ? "YES" : "NO");
    }
}

来下围棋吧~TensorFlow minigo~

TensorFlow 早些天发布了一个名为 minigo 的项目,因为 Google 官方还一直没有开源 AlphaZero,那么就先来看看 minigo 怎么玩(搞事情)吧www

假设乃使用的是 macOS / Ubuntu / debian,当然其他系统也可以,操作大同小异。

首先是安装 Python 3,macOS 下默认是 python2.7,新的 Python 3 需要去 Python 官网下载,https://www.python.org/downloads/。对于 Ubuntu / debian 来说的话,则是直接

apt-get install python3

当然,比较新的 Ubuntu / debian 都会默认安装 python3.6~

接下来需要的是 pip,也就是那个最常被用来安装、管理 Python 包的工具~对于以上三个系统,都可以在terminal中执行如下语句安装w

curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
python3 get-pip.py

随后就可以用 pip 安装virtualenv了,virtualenv用于隔离不同项目的环境,以避免个项目之间的依赖冲突。

pip3 install virtualenv
pip3 install virtualenvwrapper

之后就可以生成一个用于 minigo 的环境了~

export WORKON_HOME=~/.virtualenvs
export VIRTUALENVWRAPPER_PYTHON=$(which python3)

稍有不同的是,macOS 中,virtualenvwrapper.sh 的安装位置可能不在 /usr/local/bin/virtualenvwrapper.sh。而是在 python3 本体的目录下。于是这里对于 macOS 来说,需要执行的是

export VIRTUALENVWRAPPER_SCRIPT="`dirname $VIRTUALENVWRAPPER_PYTHON`/virtualenvwrapper.sh"

而对于 Ubuntu / debian 则是

export VIRTUALENVWRAPPER_SCRIPT="/usr/local/bin/virtualenvwrapper.sh"

接下来的步骤则都是相同的了~

初始化virtualenvwrapper~指定 python3 创建名为 minigo 的环境

source $VIRTUALENVWRAPPER_SCRIPT
mkvirtualenv -p `which python3` minigo

第一次创建时会自动切换到该环境上,之后需要切换进来则是

source $VIRTUALENVWRAPPER_SCRIPT
workon minigo

现在到正式开始 minigo 的部分了~macOS / Ubuntu / debian 都有自带 git,于是就直接开始w

git clone https://github.com/tensorflow/minigo.git
cd minigo
pip3 install -r requirements.txt

接下来,如果乃的机器上有支持 CUDA 的 GPU 的话,则推荐安装 CUDA9.0,并且用 GPU 版本的 TensorFlow

pip3 install "tensorflow-gpu>=1.5,<1.6"

若不满足条件的话,则是

pip3 install "tensorflow>=1.5,<1.6"

安装好之后,就可以开始设置 minigo 的环境了w

# 用于 cluster/common.sh 的环境变量
PROJECT=minigo-project
source cluster/common.sh

# 我们将训练的模型放在 ~/minigo-models 下
MINIGO_MODELS=$HOME/minigo-models
# 创建一会儿需要的目录
mkdir -p $MINIGO_MODELS
mkdir -p $MINIGO_MODELS/models
mkdir -p $MINIGO_MODELS/data/selfplay
mkdir -p $MINIGO_MODELS/sgf/

# 设置第一个模型的名字
export MODEL_NAME=000000-bootstrap

现在就可以开始第 1 个模型了~

# main.py bootstrap 部分的话,需要两个参数
# 第一个是工作目录,这里我们使用 "." 也就是当前目录
# 第二个是模型保存的路径,这里将前面设置好的变量拼接起来
python3 main.py bootstrap . "\$MINIGO_MODELS/models/$MODEL_NAME"

如果之前的步骤都正确的话,那么输出看上去就是类似这样的w

(minigo) root@minigo:~/minigo# python3 main.py bootstrap . \$MINIGO_MODELS/$MODEL_NAME
See tf.nn.softmax_cross_entropy_with_logits_v2.

Copying ./model.ckpt-1.meta to /root/minigo-models/000000-bootstrap.meta
Copying ./model.ckpt-1.index to /root/minigo-models/000000-bootstrap.index
Copying ./model.ckpt-1.data-00000-of-00001 to /root/minigo-models/000000-bootstrap.data-00000-of-00001
(minigo) root@ minigo:~/minigo# ls /root/minigo-models/
000000-bootstrap.data-00000-of-00001  000000-bootstrap.index  000000-bootstrap.meta

蓝后就可以让 minigo 来进行 selfplay 了~

python3 main.py selfplay "\$MINIGO_MODELS/models/$MODEL_NAME" \
  --readouts 10 \
  -v 3 \
  --output-dir="\$MINIGO_MODELS/data/selfplay/$MODEL_NAME/local_worker" \
  --output-sgf="\$MINIGO_MODELS/sgf/$MODEL_NAME/local_worker"

接下来,让 minigo 在本地进行增强学习的话,我们需要修改一下 rl_loop.py,因为虽然目前官方提供了 local_rl_loop.py,但是里面模型的名字是写死的。

修改的部分在 rl_loop.py 的第 31 行,

# 原本是这样的
# BASE_DIR = "gs://{}".format(BUCKET_NAME)
# 需要修改成下面这样
BASE_DIR = os.environ['MINIGO_MODELS']

修改好之后,则可以不需要Google Cloud Storage就能在本地进行增强学习(其实照着 rl_loop.py 去改 local_rl_loop.py 也行)

python3 rl_loop.py selfplay --readouts=10 -v 2

执行好之后,就可以把数据收集起来了

python3 main.py gather --input-directory="$MINIGO_MODELS/data/selfplay" --output-directory="$MINIGO_MODELS/data/training_chunks"

然后生成新的模型~

export NEXT_MODEL=000001-naive
python3 main.py train . "$MINIGO_MODELS/data/training_chunks" "$MINIGO_MODELS/models/$NEXT_MODEL" --generation-num=1

最后就可以继续在新的模型上训练

# 使用新的模型
export MODEL_NAME=$NEXT_MODEL
# selfplay
python3 main.py selfplay "\$MINIGO_MODELS/models/$MODEL_NAME" \
  --readouts 10 \
  -v 3 \
  --output-dir="\$MINIGO_MODELS/data/selfplay/$MODEL_NAME/local_worker" \
  --output-sgf="\$MINIGO_MODELS/sgf/$MODEL_NAME/local_worker"
# 增强学习
python3 rl_loop.py selfplay --readouts=10 -v 2

在这之后,又可以 gather、生成新模型等等~

如果想和训练好的模型对战的话,则是

LATEST_MODEL=$(ls -d $MINIGO_MODELS/models/* | tail -1 | cut -f 1 -d '.')
python3 main.py gtp -l $LATEST_MODEL -r 10 -v 3

当提示出“GTP engine ready”的时候,就可以开始玩了~

genmove white # 让 minigo 给 white 下一步
play black A1 # 自己给 black 下一步,下在 A1 这个位置
showboard # 查看现在的局面www
minigo
minigo

N皇后问题——记录下那些老死不相往来的N个女人的位置吧

于是在N个女人的老死不相往来-n皇后问题代码的基础上,增加了输出那N个老死不相往来的女人的位置的代码~

简单来说就是在每次尝试第 i 行新的摆法、并且递归的 trying 了它的下一行之后,需要还原第 i 行的状态。输出棋盘则可以直接在每次判断是可行解之后w

#include <cmath>
#include <iostream>
#include <vector>

using namespace std;

// total:记录解;finishline:确认的行
int total = 0, finishline = 1;

// row:竖直攻击; ls:斜杠攻击; rs:反斜杠攻击
void trying(long row, long ls, long rs, int current_row, vector<vector<bool>> &board)
{
    if (row != finishline) // 开始新行
    {
        long position = finishline & ~(row | ls | rs);
        while(position) // 若行间无位可放...
        {
            long pointline = position & -position;
            position -= pointline;
            
            // 放上皇后
            board[current_row][(int)log2(pointline)] = 1;
            trying(row + pointline, (ls + pointline) << 1, (rs + pointline) >> 1, current_row + 1, board);
            // 不论上面的 trying 是否成功
            // 我们都需要把这一行还原到之前的状态
            board[current_row][(int)log2(pointline)] = 0;
        }
    }
    else
    {
        total++;
        // 输出棋盘
        for (auto &row : board)
        {
            for (auto col : row)
            {
                cout << col;
            }
            cout << '\n';
        }
        cout << '\n';
    }
}

int main(int argc, char *argv[]) {
    int grid = 8; // 修改grid的话,1~31皇后都能解w
    finishline = (finishline << grid) - 1;
    
    // 初始化棋盘 调整好棋盘的大小
    vector<vector<bool>> board;
    board.resize(grid);
    for_each(board.begin(), board.end(), [grid](vector<bool> &row) {
        row.resize(grid);
    });
    
    trying(0, 0, 0, 0, board);
    cout << total << endl;
}

由前序遍历和中序遍历重构二叉树w

#include <iostream>
#include <vector>

using namespace std;

struct TreeNode {
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};

TreeNode * rebin(vector<int> pre, vector<int> in) {
    // 首先看看有米有东西吧
    // 一个元素都没有的话
    // 这个二叉树就只有一个NULL节点
    // 直接返回即可w
    if (pre.size() == 0) return nullptr;
    
    // 根据前序遍历的定义 根节点一定在前序遍历结果的第一个位置上
    // 而中序遍历的话 根结点必然需要等到它的左子树都输出完才会出现
    // 利用这一点我们分开左右子树
    int root_value = pre[0];
    TreeNode * root = new TreeNode(root_value);
    // 找到根节点在中序遍历里的下标
    // 以此分开左右子树
    int root_index_of_in = 0;
    for (int i = 0; i < in.size(); i++) {
        if (in[i] == root_value) {
            root_index_of_in = i;
            break;
        }
    }
    // 分开左右子树
    // 按原始的顺序分别保存左右子树的前序遍历和中序遍历
    vector<int> left_pre, left_in, right_pre, right_in;
    // 先是左子树
    for (int i = 0; i < root_index_of_in; i++) {
        // 这里需要+1
        // 因为前序遍历的话 第一个元素是根节点
        // 所以需要跳过当前(下标为0)的这个元素
        left_pre.emplace_back(pre[i+1]);
        left_in.emplace_back(in[i]);
    }
    // 然后是右子树
    for (int i = root_index_of_in + 1; i < in.size(); i++) {
        right_pre.emplace_back(pre[i]);
        right_in.emplace_back(in[i]);
    }
    // 既然左右子树的前序遍历和中序遍历都有了
    // 和我们一开始拿到的初始条件很相似吧~
    // 那么递归吧w
    root->left = rebin(left_pre, left_in);
    root->right = rebin(right_pre, right_in);
    return root;
}

// 三种遍历方式~/
typedef enum {
    PRE_ORDER,
    IN_ORDER,
    POST_ORDER,
} TreeTraversal;

void print_with_order(TreeNode * root, TreeTraversal order) {
    // 如果根节点是空的
    // 那啥都不用说直接返回吧
    if (root == nullptr) return;
    
    // 看看使用什么序来遍历
    // 本质上仅仅只是输出当前root节点的位置不一样
    if (order == PRE_ORDER) {
        cout << root->val << ' ';
        print_with_order(root->left, order);
        print_with_order(root->right, order);
    } else if (order == IN_ORDER) {
        print_with_order(root->left, order);
        cout << root->val << ' ';
        print_with_order(root->right, order);
    } else {
        print_with_order(root->left, order);
        print_with_order(root->right, order);
        cout << root->val << ' ';
    }
}

int main(int argc, const char * argv[]) {
    vector<int> pre = {1,2,4,7,3,5,6,8};
    vector<int> in  = {4,7,2,1,5,3,8,6};
    TreeNode * head = rebin(pre, in);
    
    // 用构建好的树输出前序和中序遍历来验证~
    cout << "pre: ";
    print_with_order(head, PRE_ORDER);
    cout << endl;
    
    cout << "in:  ";
    print_with_order(head, IN_ORDER);
    cout << endl;
}

探索网易云音乐——用 Python 写个爬虫吧w

Scrap Netease!

既然想到要写点什么东西的话,那么就来试试网易云音乐吧w

那么第一步,我们要有数据才行,网易也当然不会公开自己的数据库啦,于是我们得用爬虫才行~

Python 语言里可以选择的爬虫还是不少,比如 Scrapy,然后在一开始开开心心的选择了 scrapy,之后发现网易云音乐对于 IP 的访问频率有限制,一旦超过限制,就会触发网易云的保护机制,暂时进入网易云黑名单,此时再访问的话,服务器返回的就都是 HTTP 503 - Service Unavailable。

解决方法说简单也简单,说麻烦也麻烦。简单在于,我的网络是电信的,于是只要重新拨号就能拿到新的公网 IP,写重新拨号的脚本也很简单。而稍有麻烦的地方在于,scrapy 是多线程的,在自己处理网易云返回 503 的时候,要注意重新拨号的脚本只需要执行一次。

如下图所示,假设我们的爬虫是 4 个线程,线程 2 在 t1 时刻访问时,遇到了 HTTP 503 之后,它就会进入我们处理网易云音乐 HTTP 503 的 handler。而对于其他线程来说,如果在 t1 时刻之前就开始访问了的话,那么是没有问题的,比如线程 1;如果是在 t1 时刻之后的话,比如线程 3、4,那么它们也会遇到 HTTP 503,但是这个时候,线程 3、4 就不用再走一次 handler,而是等待然后重试就可以。

HTTP 503 Handler
HTTP 503 Handler

但是也不用这么费劲,既然网易云音乐有频率限制,使用多线程的话,反而会更快的触发网易云的黑名单机制,因此,顺便作为重造轮子,我们来自己写个爬虫吧/

继续阅读“探索网易云音乐——用 Python 写个爬虫吧w”

原来夜晚也如此明亮

知らなかだね。夜かこなに明るいなんて。

知らなかだね。夜かこなに明るいなんて。
我都不知道呢。夜晚原来也是如此的光明。

这是战争之后,和小千坐着 Kettenkrad 从人类文明几乎灭绝的末日废墟里到了地面上的尤说的话。

从表面上来看,这是自然而然的,毕竟她们在几乎没有光的建筑里过了好久好久,眼睛早已习惯了黑暗,所以在刚出来的时候会觉得就连夜晚也好明亮。

小千和尤
小千和尤

但是既然我们已经知道这部作品里包含了很多隐晦的关于人性,战争,政治等等的思考,那么这里是在说什么呢?

傻可的理解是,对于生于和平的人来说,她们司空见惯的每一个黑暗夜晚,可能都是另一些人所盼望的光明。即便我们的「今天」并非是一帆风顺,但也是另一些人渴望而几乎不可达的的「明天」。

当然这么说也绝不是让我们再去重温什么「通讯基本靠吼,交通基本靠走,食物基本米有」的时代,而是希望我们人类能够维持并且继续发展当下的美好,即使它们目前也有很多不完美。

我们利用 $E=mc^2$ 制造了核武器,随后又谨慎的通过国际上的政治会谈等尽努力不让核战争爆发。这就像是我们将脖子放进了上吊的圈环里,然后尽力的不踏翻脚下的凳子一样。如果再一次打起仗来,我们也绝不可能再舒适的坐在电脑前,真正到了那个时候,在北风呼啸,雪落纷纷,只有一碗清汤(而且还是庆祝用)喝的夜晚,我们也会怀念「此刻」,怀念它的美好,怀念它的不美好,也更是会后悔为什么以前没有好好珍惜。

汤。
汤。

在第一话快要结束时,有这么一段对话~

尤莉「没想到竟会落到靠吃雪维生」
尤莉「战争真是讨厌啊」
小千「你有什么资格说」

「お前が言うな」
「お前が言うな」

那么在这一处,也是另有所指的

翻译过来是「你有什么资格说」,其实在日语里,「お前が言うな」,这句话除了表面意思之外,其实还暗示「你」当时就是那个最支持战争的人。

在这里,我们不必探寻在战争的时候尤莉具体是什么样的,傻可猜想作者也只是借着小千和尤说出了这么一件事——战前那些高呼好好好、支持的人,在真正上了战场之后,体会到了什么叫做战争的残忍之后,才来反思战争、为什么要打仗。

少女终末旅行,可能会是以后哪一天人类用战争将自己推向穷途末路时的反思之旅吧。

烟囱城的普佩尔——灵魂与梦想

Stars

“我会一直相信下去,即使只有我一个人。”

在一座头顶的天空永远永远是烟雾蒙蒙的城市里,人们从来没有见过真正的天空,也没见过一闪一闪的星星,在这里讨论星星,是会被笑话甚至遭受排挤的。

当我们在现实中,在某些环境下跟别人讨论梦想——“我想尝试另一个行业”、“我想发一篇顶会论文”——的时候,偶尔也会被身边的人不理解、笑话(虽然他们不一定是恶意的嘲笑)。

在绘本里,普佩尔原本是一个被邮递员不小心掉落到垃圾堆里的,咚咚乱跳的心脏,它在吸附了一件件垃圾之后,形成了垃圾人,也就是普佩尔。那么在这样的城市里,「垃圾」是什么呢,或者说在这里人们抛弃的是什么呢,大概是「梦想」吧。

那么按理说一个饱含着梦想的灵魂应该是美好的,为什么这里以一个非常脏、非常臭的形象出现呢?傻喵以为,在这个城市中,梦想是被看作不切实际、不被需要的「肮脏」之物,对于梦想的谈论、行动就更是让人厌恶之事。

于是乎,普佩尔嘴里“噗斯—哈、哈”地吐着瓦斯气体,我们知道瓦斯气体里面是添加了有臭鸡蛋味的硫化氢的,一般闻到之后,都会赶紧避开。这个「瓦斯气体」大概就是比喻现实生活中我们跟别人讨论自己理想时,别人对我们嘴里话语的看法吧。鲁比齐的爸爸也正是因为见到了烟雾之上的星星,而被城里的人们排斥、叫做骗子。

在经历了冷眼、排斥之后,在某个夜里,之前听闻鲁比奇说过浓雾之上有星星的普佩尔拉着鲁比奇到了船上,用自己嘴里的「瓦斯气体」吹满气球,载着鲁比奇穿过了烟雾。

也终于云开见月明。

Stars
Stars

固然,梦想是美好的,现实里要实现梦想也是会有各种艰难险阻的,但是这不意味着放弃,或许正如作者所说,我们会对现实有所妥协,但在那之后,我们又会将丢弃的东西重新拾回。

生きるという喜びも
不仅是活下去的喜悦

辛さも教えたのデス
连活下去的艰辛也教会了我Death

 

再来小小地说下灵魂~

普佩尔是夜空中穿梭的邮递员不小心落在烟囱城的心脏,绘本最后也交代了这其实就是鲁比齐的充满梦想的爸爸。那么,一个有梦想的灵魂也许就是上天给予人世间最美好的礼物吧~

乃说灵魂是怎么装进身体里面的呢?

虽然不知道完整的、确切的答案,但是,其中粘合剂的一部分肯定是由梦想组成的呢/

言语韵律中的思想与美

在没有文字的时候,韵律——神话、诗歌——皆是口耳相传,尔后它们又因文字的出现而有了实形的记载,也有了更丰富的表现形式。在言语或文字中透着的那些韵律,都是某种思想的表达。可可认为,思想是一个人的文化、生活背景以及那个人所经历的事情所造就的一种产物。记得在我所学的《古希腊喜剧选读》的课上,讲师是这么解释“文化”的。

“文化”(cultura)首先意味着农作:对土壤及其作物的培育,对土壤的照料,以及按其本性对土壤质量的提升。“文化”衍生性地、且在今天主要地意味着对心灵的培育,按心灵本性对其内在能力的照料和提升。就像土壤需要其培育者那样,心灵需要老师。

那么什么样的人才能够称作是我们“心灵的老师”呢,那些老师要在何处去寻觅呢?一般来说,一个伟大的人会有一个伟大的老师,而老师也会有他自己的老师,但终究我们会找到那么一些人,他们没有老师,他们正是那些伟大的心灵(Great Minds)。

当我们从咿呀学语一步步走到自己文化生命的开端时,哪些伟大的心灵是我们应该去找寻的呢?埃斯库罗斯曾经说过:“对于那些个孩子们应该由老师来教,而成年人则应该由诗人来教”。可见那些诗词言语中所蕴含的韵律,正是我们心灵的老师。

以古希腊的城邦教育为例。不论是严肃的肃剧还是轻快的谐剧,它们都和宗教祭祀相关——每年一度的酒神祭。当时的僭主,庇西斯特拉图(Peisistratus)将酒神祭拜表演引入雅典城邦,创办了戏剧节,而其原因则是——有抱负的统治者必须陶铸人民的性情,而戏剧就充当了民众的德育“教材”:通过戏剧,城邦民众反观自己的言行、审查自己的政治意见、雕琢自己的城邦美德。

不论是讲究平仄的中国诗歌,还是五/七/五/七/七的日本短歌,又或者是古希腊那一本本厚厚的戏剧台本。在这些言语文字韵律之间,人世间的哲思、喜乐哀愁、思念与爱都包括进来了,而这些美好的情感随着韵律,终传递到空间和时间的远方,在听者心中泛起波浪。也许正如——

不要再睡,好妻子,
唱你美妙的歌曲,
哀悼我们伊提斯,
黄颔百啭声无已,
清音穿过忍冬丛,
遥遥直达天帝宫,
使得金发太阳神,
邀神起舞击像筝,
引起众神齐唱和,
唱和你的悼亡歌。

——节选自《鸟》阿里斯托芬