2020年3月

让我们使用 C99 和 C++11 完成常见的数据科学任务。

虽然 PythonR 之类的语言在数据科学中越来越受欢迎,但是 C 和 C++ 对于高效的数据科学来说是一个不错的选择。在本文中,我们将使用 C99C++11 编写一个程序,该程序使用 Anscombe 的四重奏数据集,下面将对其进行解释。

我在一篇涉及 Python 和 GNU Octave 的文章中写了我不断学习编程语言的动机,值得大家回顾。这里所有的程序都需要在命令行上运行,而不是在图形用户界面(GUI)上运行。完整的示例可在 polyglot\_fit 存储库中找到。

编程任务

你将在本系列中编写的程序:

  • CSV 文件中读取数据
  • 用直线插值数据(即 f(x)=m ⋅ x + q
  • 将结果绘制到图像文件

这是许多数据科学家遇到的普遍情况。示例数据是 Anscombe 的四重奏的第一组,如下表所示。这是一组人工构建的数据,当拟合直线时可以提供相同的结果,但是它们的曲线非常不同。数据文件是一个文本文件,其中的制表符用作列分隔符,前几行作为标题。该任务将仅使用第一组(即前两列)。

C 语言的方式

C 语言是通用编程语言,是当今使用最广泛的语言之一(依据 TIOBE 指数RedMonk 编程语言排名编程语言流行度指数GitHub Octoverse 状态 得来)。这是一种相当古老的语言(大约诞生在 1973 年),并且用它编写了许多成功的程序(例如 Linux 内核和 Git 仅是其中的两个例子)。它也是最接近计算机内部运行机制的语言之一,因为它直接用于操作内存。它是一种编译语言;因此,源代码必须由编译器转换为机器代码。它的标准库很小,功能也不多,因此人们开发了其它库来提供缺少的功能。

我最常在数字运算中使用该语言,主要是因为其性能。我觉得使用起来很繁琐,因为它需要很多样板代码,但是它在各种环境中都得到了很好的支持。C99 标准是最新版本,增加了一些漂亮的功能,并且得到了编译器的良好支持。

我将一路介绍 C 和 C++ 编程的必要背景,以便初学者和高级用户都可以继续学习。

安装

要使用 C99 进行开发,你需要一个编译器。我通常使用 Clang,不过 GCC 是另一个有效的开源编译器。对于线性拟合,我选择使用 GNU 科学库。对于绘图,我找不到任何明智的库,因此该程序依赖于外部程序:Gnuplot。该示例还使用动态数据结构来存储数据,该结构在伯克利软件分发版(BSD)中定义。

Fedora 中安装很容易:

sudo dnf install clang gnuplot gsl gsl-devel

代码注释

在 C99 中,注释)的格式是在行的开头放置 //,行的其它部分将被解释器丢弃。另外,/**/ 之间的任何内容也将被丢弃。

// 这是一个注释,会被解释器忽略
/* 这也被忽略 */

必要的库

库由两部分组成:

  • 头文件,其中包含函数说明
  • 包含函数定义的源文件

头文件包含在源文件中,而库文件的源文件则链接到可执行文件。因此,此示例所需的头文件是:

// 输入/输出功能
#include <stdio.h>
// 标准库
#include <stdlib.h>
// 字符串操作功能
#include <string.h>
// BSD 队列
#include <sys/queue.h>
// GSL 科学功能
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>

主函数

在 C 语言中,程序必须位于称为主函数 main() 的特殊函数内:

int main(void) {
    ...
}

这与上一教程中介绍的 Python 不同,后者将运行在源文件中找到的所有代码。

定义变量

在 C 语言中,变量必须在使用前声明,并且必须与类型关联。每当你要使用变量时,都必须决定要在其中存储哪种数据。你也可以指定是否打算将变量用作常量值,这不是必需的,但是编译器可以从此信息中受益。 以下来自存储库中的 fitting\_C99.c 程序

const char *input_file_name = "anscombe.csv";
const char *delimiter = "\t";
const unsigned int skip_header = 3;
const unsigned int column_x = 0;
const unsigned int column_y = 1;
const char *output_file_name = "fit_C99.csv";
const unsigned int N = 100;

C 语言中的数组不是动态的,从某种意义上说,数组的长度必须事先确定(即,在编译之前):

int data_array[1024];

由于你通常不知道文件中有多少个数据点,因此请使用单链列表。这是一个动态数据结构,可以无限增长。幸运的是,BSD 提供了链表。这是一个示例定义:

struct data_point {
    double x;
    double y;

    SLIST_ENTRY(data_point) entries;
};

SLIST_HEAD(data_list, data_point) head = SLIST_HEAD_INITIALIZER(head);
SLIST_INIT(&head);

该示例定义了一个由结构化值组成的 data_point 列表,该结构化值同时包含 x 值和 y 值。语法相当复杂,但是很直观,详细描述它就会太冗长了。

打印输出

要在终端上打印,可以使用 printf() 函数,其功能类似于 Octave 的 printf() 函数(在第一篇文章中介绍):

printf("#### Anscombe's first set with C99 ####\n");

printf() 函数不会在打印字符串的末尾自动添加换行符,因此你必须添加换行符。第一个参数是一个字符串,可以包含传递给函数的其他参数的格式信息,例如:

printf("Slope: %f\n", slope);

读取数据

现在来到了困难的部分……有一些用 C 语言解析 CSV 文件的库,但是似乎没有一个库足够稳定或流行到可以放入到 Fedora 软件包存储库中。我没有为本教程添加依赖项,而是决定自己编写此部分。同样,讨论这些细节太啰嗦了,所以我只会解释大致的思路。为了简洁起见,将忽略源代码中的某些行,但是你可以在存储库中找到完整的示例代码。

首先,打开输入文件:

FILE* input_file = fopen(input_file_name, "r");

然后逐行读取文件,直到出现错误或文件结束:

while (!ferror(input_file) && !feof(input_file)) {
    size_t buffer_size = 0;
    char *buffer = NULL;
   
    getline(&buffer, &buffer_size, input_file);

    ...
}

getline() 函数是 POSIX.1-2008 标准新增的一个不错的函数。它可以读取文件中的整行,并负责分配必要的内存。然后使用 strtok() 函数将每一行分成 字元 token 。遍历字元,选择所需的列:

char *token = strtok(buffer, delimiter);

while (token != NULL)
{
    double value;
    sscanf(token, "%lf", &value);

    if (column == column_x) {
        x = value;
    } else if (column == column_y) {
        y = value;
    }

    column += 1;
    token = strtok(NULL, delimiter);
}

最后,当选择了 xy 值时,将新数据点插入链表中:

struct data_point *datum = malloc(sizeof(struct data_point));
datum->x = x;
datum->y = y;

SLIST_INSERT_HEAD(&head, datum, entries);

malloc() 函数为新数据点动态分配(保留)一些持久性内存。

拟合数据

GSL 线性拟合函数 gslfitlinear() 期望其输入为简单数组。因此,由于你将不知道要创建的数组的大小,因此必须手动分配它们的内存:

const size_t entries_number = row - skip_header - 1;

double *x = malloc(sizeof(double) * entries_number);
double *y = malloc(sizeof(double) * entries_number);

然后,遍历链表以将相关数据保存到数组:

SLIST_FOREACH(datum, &head, entries) {
    const double current_x = datum->x;
    const double current_y = datum->y;

    x[i] = current_x;
    y[i] = current_y;

    i += 1;
}

现在你已经处理完了链表,请清理它。要总是释放已手动分配的内存,以防止内存泄漏。内存泄漏是糟糕的、糟糕的、糟糕的(重要的话说三遍)。每次内存没有释放时,花园侏儒都会找不到自己的头:

while (!SLIST_EMPTY(&head)) {
    struct data_point *datum = SLIST_FIRST(&head);

    SLIST_REMOVE_HEAD(&head, entries);

    free(datum);
}

终于,终于!你可以拟合你的数据了:

gsl_fit_linear(x, 1, y, 1, entries_number,
               &intercept, &slope,
               &cov00, &cov01, &cov11, &chi_squared);
const double r_value = gsl_stats_correlation(x, 1, y, 1, entries_number);

printf("Slope: %f\n", slope);
printf("Intercept: %f\n", intercept);
printf("Correlation coefficient: %f\n", r_value);

绘图

你必须使用外部程序进行绘图。因此,将拟合数据保存到外部文件:

const double step_x = ((max_x + 1) - (min_x - 1)) / N;

for (unsigned int i = 0; i < N; i += 1) {
    const double current_x = (min_x - 1) + step_x * i;
    const double current_y = intercept + slope * current_x;

    fprintf(output_file, "%f\t%f\n", current_x, current_y);
}

用于绘制两个文件的 Gnuplot 命令是:

plot 'fit_C99.csv' using 1:2 with lines title 'Fit', 'anscombe.csv' using 1:2 with points pointtype 7 title 'Data'

结果

在运行程序之前,你必须编译它:

clang -std=c99 -I/usr/include/ fitting_C99.c -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_C99

这个命令告诉编译器使用 C99 标准、读取 fitting_C99.c 文件、加载 gslgslcblas 库、并将结果保存到 fitting_C99。命令行上的结果输出为:

#### Anscombe's first set with C99 ####
Slope: 0.500091
Intercept: 3.000091
Correlation coefficient: 0.816421

这是用 Gnuplot 生成的结果图像:

 title=

C++11 方式

C++ 语言是一种通用编程语言,也是当今使用的最受欢迎的语言之一。它是作为 C 的继承人创建的(诞生于 1983 年),重点是面向对象程序设计(OOP)。C++ 通常被视为 C 的超集,因此 C 程序应该能够使用 C++ 编译器进行编译。这并非完全正确,因为在某些极端情况下它们的行为有所不同。 根据我的经验,C++ 与 C 相比需要更少的样板代码,但是如果要进行面向对象开发,语法会更困难。C++11 标准是最新版本,增加了一些漂亮的功能,并且基本上得到了编译器的支持。

由于 C++ 在很大程度上与 C 兼容,因此我将仅强调两者之间的区别。我在本部分中没有涵盖的任何部分,则意味着它与 C 中的相同。

安装

这个 C++ 示例的依赖项与 C 示例相同。 在 Fedora 上,运行:

sudo dnf install clang gnuplot gsl gsl-devel

必要的库

库的工作方式与 C 语言相同,但是 include 指令略有不同:

#include <cstdlib>
#include <cstring>
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>

extern "C" {
#include <gsl/gsl_fit.h>
#include <gsl/gsl_statistics_double.h>
}

由于 GSL 库是用 C 编写的,因此你必须将这个特殊情况告知编译器。

定义变量

与 C 语言相比,C++ 支持更多的数据类型(类),例如,与其 C 语言版本相比,string 类型具有更多的功能。相应地更新变量的定义:

const std::string input_file_name("anscombe.csv");

对于字符串之类的结构化对象,你可以定义变量而无需使用 = 符号。

打印输出

你可以使用 printf() 函数,但是 cout 对象更惯用。使用运算符 << 来指示要使用 cout 打印的字符串(或对象):

std::cout << "#### Anscombe's first set with C++11 ####" << std::endl;

...

std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;

读取数据

该方案与以前相同。将打开文件并逐行读取文件,但语法不同:

std::ifstream input_file(input_file_name);

while (input_file.good()) {
    std::string line;

    getline(input_file, line);

    ...
}

使用与 C99 示例相同的功能提取行字元。代替使用标准的 C 数组,而是使用两个向量。向量是 C++ 标准库中对 C 数组的扩展,它允许动态管理内存而无需显式调用 malloc()

std::vector<double> x;
std::vector<double> y;

// Adding an element to x and y:
x.emplace_back(value);
y.emplace_back(value);

拟合数据

要在 C++ 中拟合,你不必遍历列表,因为向量可以保证具有连续的内存。你可以将向量缓冲区的指针直接传递给拟合函数:

gsl_fit_linear(x.data(), 1, y.data(), 1, entries_number,
               &intercept, &slope,
               &cov00, &cov01, &cov11, &chi_squared);
const double r_value = gsl_stats_correlation(x.data(), 1, y.data(), 1, entries_number);

std::cout << "Slope: " << slope << std::endl;
std::cout << "Intercept: " << intercept << std::endl;
std::cout << "Correlation coefficient: " << r_value << std::endl;

绘图

使用与以前相同的方法进行绘图。 写入文件:

const double step_x = ((max_x + 1) - (min_x - 1)) / N;

for (unsigned int i = 0; i < N; i += 1) {
    const double current_x = (min_x - 1) + step_x * i;
    const double current_y = intercept + slope * current_x;

    output_file << current_x << "\t" << current_y << std::endl;
}

output_file.close();

然后使用 Gnuplot 进行绘图。

结果

在运行程序之前,必须使用类似的命令对其进行编译:

clang++ -std=c++11 -I/usr/include/ fitting_Cpp11.cpp -L/usr/lib/ -L/usr/lib64/ -lgsl -lgslcblas -o fitting_Cpp11

命令行上的结果输出为:

#### Anscombe's first set with C++11 ####
Slope: 0.500091
Intercept: 3.00009
Correlation coefficient: 0.816421

这就是用 Gnuplot 生成的结果图像:

 title=

结论

本文提供了用 C99 和 C++11 编写的数据拟合和绘图任务的示例。由于 C++ 在很大程度上与 C 兼容,因此本文利用了它们的相似性来编写了第二个示例。在某些方面,C++ 更易于使用,因为它部分减轻了显式管理内存的负担。但是其语法更加复杂,因为它引入了为 OOP 编写类的可能性。但是,仍然可以用 C 使用 OOP 方法编写软件。由于 OOP 是一种编程风格,因此可以在任何语言中使用。在 C 中有一些很好的 OOP 示例,例如 GObjectJansson库。

对于数字运算,我更喜欢在 C99 中进行,因为它的语法更简单并且得到了广泛的支持。直到最近,C++11 还没有得到广泛的支持,我倾向于避免使用先前版本中的粗糙不足之处。对于更复杂的软件,C++ 可能是一个不错的选择。

你是否也将 C 或 C++ 用于数据科学?在评论中分享你的经验。


via: https://opensource.com/article/20/2/c-data-science

作者:Cristiano L. Fontana 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

又是周一,你正在为你的老板 Lumbergh (LCTT 译注:《上班一条虫》中的副总裁)努力倒腾那些 无聊之极的文档。为什么不玩玩 Emacs 中类似 zork 的文字冒险游戏来让你的大脑从单调的工作中解脱出来呢?

但说真的,Emacs 中既有游戏,也有古怪的玩物。有些你可能有所耳闻。这些玩意唯一的共同点就是,它们大多是很久以前就添加到 Emacs 中的:有些东西真的是相当古怪(如你将在下面看到的),而另一些则显然是由无聊的员工或学生们编写的。它们全有一个共同点,都带着一种奇思妙想和随意性,这在今天的 Emacs 中很少见。Emacs 现在变得十分严肃,在某种程度上,它已经与 20 世纪 80 年代那些游戏被编写出来的时候大不一样。

汉诺塔

汉诺塔 是一款古老的数学解密游戏,有些人可能对它很熟悉,因为它的递归和迭代解决方案经常被用于计算机科学教学辅助。

Tower of Hanoi Screenshot

Emacs 中有三个命令可以运行汉诺塔:M-x hanoi 默认为 3 个碟子; M-x hanoi-unixM-x hanoi-unix-64 使用 unix 时间戳的位数(32 位或 64 位)作为默认盘子的个数,并且每秒钟自动移动一次,两者不同之处在于后者假装使用 64 位时钟(因此有 64 个碟子)。

Emacs 中汉诺塔的实现可以追溯到 20 世纪 80 年代中期——确实是久得可怕。它有一些自定义选项(M-x customize-group RET hanoi RET),如启用彩色碟子等。当你离开汉诺塔缓冲区或输入一个字符,你会收到一个讽刺的告别信息(见上图)。

5x5

5x5 game grid

5x5 的游戏是一个逻辑解密游戏:你有一个 5x5 的网格,中间的十字被填满;你的目标是通过按正确的顺序切换它们的空满状态来填充所有的单元格,从而获得胜利。这并不像听起来那么容易!

输入 M-x 5x5 就可以开始玩了,使用可选的数字参数可以改变网格的大小。这款游戏的有趣之处在于它能向你建议下一步行动并尝试找到该游戏网格的解法。它用到了 Emacs 自己的一款非常酷的符号 RPN 计算器 M-x calc(在《Emacs 快乐计算》这篇文章中,我使用它来解决了一个简单的问题)。

所以我喜欢这个游戏的原因是它提供了一个非常复杂的解题器——真的,你应该通过 M-x find-library RET 5x5 来阅读其源代码——这是一个试图通过暴力破解游戏解法的“破解器”。

创建一个更大的游戏网格,例如输入 M-10 M-x 5x5,然后运行下面某个 crack 命令。破解器将尝试通过迭代获得最佳解决方案。它会实时运行该游戏,观看起来非常有趣:

  • M-x 5x5-crack-mutating-best: 试图通过变异最佳解决方案来破解 5x5。
  • M-x 5x5-crack-mutating-current: 试图通过变异当前解决方案来破解 5x5。
  • M-x 5x5-crack-random: 尝试使用随机方案解破解 5x5。
  • M-x 5x5-crack-xor-mutate: 尝试通过将当前方案和最佳方案进行异或运算来破解 5x5。

文本动画

你可以通过运行 M-x animation-birthday-present 并给出你的名字来显示一个奇特的生日礼物动画。它看起来很酷!

xkcd

这里用的 animate 包也用在了 M-x butterfly 命令中,这是一个向上面的 XKCD 漫画致敬而添加到 Emacs 中的命令。当然,漫画中的 Emacs 命令在技术上是无效的,但它的幽默足以弥补这一点。

黑箱

我将逐字引用这款游戏的目标:

游戏的目标是通过向黑盒子发射光线来找到四个隐藏的球。有四种可能: 1) 射线将通过盒子不受干扰; 2) 它将击中一个球并被吸收; 3) 它将偏转并退出盒子,或 4) 立即偏转,甚至不能进入盒子。

所以,这有点像我们小时候玩的战舰游戏),但是……是专为物理专业高学历的人准备的吧?

这是另一款添加于 20 世纪 80 年代的游戏。我建议你输入 C-h f blackbox 来阅读玩法说明(文档巨大)。

泡泡

Bubbles game

M-x bubble 游戏相当简单:你必须用尽可能少移动清除尽可能多的“泡泡”。当你移除气泡时,其他气泡会掉落并粘在一起。这是一款有趣的游戏,此外如果你使用 Emacs 的图形用户界面,它还支持图像显示。而且它还支持鼠标。

你可以通过调用 M-x bubbles-set-game-<difficulty> 来设置难度,其中 <difficulty> 可以是这些之一:easymediumdifficultharduserdefined。此外,你可以使用:M-x custom-group bubbles 来更改图形、网格大小和颜色。

由于它即简单又有趣,这是 Emacs 中我最喜欢的游戏之一。

幸运饼干

我喜欢 fortune 命令。每当我启动一个新 shell 时,这些与文学片段、谜语相结合的刻薄、无益、常常带有讽刺意味的“建议”就会点亮我的一天。

令人困惑的是,Emacs 中有两个包或多或少地做着类似的事情:fortunecookie。前者主要用于在电子邮件签名中添加幸运饼干消息,而后者只是一个简单的 fortune 格式阅读器。

不管怎样,使用 Emacs 的 cookie 包前,你首先需要通过 customize-option RET cookie RET 来自定义变量 cookie-file 告诉它从哪找到 fortune 文件。

如果你的操作系统是 Ubuntu,那么你先安装 fortune 软件包,然后就能在 /usr/share/games/fortune/ 目录中找到这些文件了。

之后你就可以调用 M-x cookie 随机显示 fortune 内容,或者,如果你想的话,也可以调用 M-x cookie-apropos 查找所有匹配的饼干。

破译器

这个包完美地抓住了 Emacs 的功利本质:这个包为你破解简单的替换密码(如“密码谜题”)提供了一个很有用的界面。你知道,二十多年前,有些人确实迫切需要破解很多基本的密码。正是像这个模块这样的小玩意让我非常高兴地用起 Emacs 来:一个只对少数人有用的模块,但是,如果你突然需要它了,那么它就在那里等着你。

那么如何使用它呢?让我们假设使用 “rot13” 密码:在 26 个字符的字母表中,将字符旋转 13 个位置。 通过 M-x ielm (Emacs 用于 运行 Elisp 的 REPL 环境)可以很容易在 Emacs 中进行尝试:

*** Welcome to IELM ***  Type (describe-mode) for help.
ELISP> (rot13 "Hello, World")
"Uryyb, Jbeyq"
ELISP> (rot13 "Uryyb, Jbeyq")
"Hello, World"
ELISP>

简而言之,你将明文旋转了 13 个位置,就得到了密文。你又旋转了一次 13 个位置,就返回了最初的明文。 这就是这个包可以帮助你解决的问题。

那么,decipher 模块又是如何帮助我们的呢?让我们创建一个新的缓冲区 test-cipher 并输入你的密文(在我的例子中是 Uryyb,Jbeyq)。

你现在面对的是一个相当复杂的界面。现在把光标放在紫色行的密文的任意字符上,并猜测这个字符可能是什么:Emacs 将根据你的选择更新其他明文的猜测结果,并告诉你目前为止字母表中的字符是如何分配的。

你现在可以用下面各种助手命令来关闭选项,以帮助推断密码字符可能对应的明文字符:

  • D: 列出示意图(该加密算法中双字符对)及其频率
  • F: 表示每个密文字母的频率
  • N: 显示字符的邻近信息。我不确定这是干啥的。
  • MR: 保存和恢复一个检查点,允许你对工作进行分支以探索破解密码的不同方法。

总而言之,对于这样一个深奥的任务,这个包是相当令人印象深刻的!如果你经常破解密码,也许这个程序包能帮上忙?

医生

doctor

啊,Emacs 医生。其基于最初的 ELIZA,“医生”试图对你说的话进行心理分析,并试图把问题复述给你。体验几分钟,相当有趣,它也是 Emacs 中最著名的古怪玩意之一。你可以使用 M-x doctor 来运行它。

Dunnet

Emacs 自己特有的类 Zork 文字冒险游戏。输入 M-x dunnet 就能玩了。这是一款相当不错的游戏,简单的说,它是另一款非常著名的 Emacs 游戏,很少有人真正玩到通关。

如果你发现自己能在无聊的文档工作之间空出时间来,那么这是一个超级棒的游戏,内置“老板屏幕”,因为它是纯文本的。

哦,还有,不要想着吃掉那块 CPU 卡 :)

五子棋

gomoku

另一款写于 20 世纪 80 年代的游戏。你必须将 5 个方块连成一条线,井字棋风格。你可以运行 M-x gomoku 来与 Emacs 对抗。游戏还支持鼠标,非常方便。你也可以自定义 gomoku 组来调整网格的大小。

生命游戏

康威的生命游戏 是细胞自动机的一个著名例子。Emacs 版本提供了一些启动模式,你可以(通过 elisp 编程)调整 life-patterns 变量来更改这些模式。

你可以用 M-x life 触发生命游戏。事实上,所有的东西,包括显示代码、注释等等一切,总共不到 300 行,这也让人印象深刻。

乒乓,贪吃蛇和俄罗斯方块

tetris

这些经典游戏都是使用 Emacs 包 gamegrid 实现的,这是一个用于构建网格游戏(如俄罗斯方块和贪吃蛇)的通用框架。gamegrid 包的伟大之处在于它同时兼容图形化和终端 Emacs:如果你在 GUI 中运行 Emacs,你会得到精美的图形;如果你没有,你看到简单的 ASCII 艺术。

你可以通过输入 M-x pongM-x snakeM-x tetris 来运行这些游戏。

特别是俄罗斯方块游戏实现的非常到位,会逐渐增加速度并且能够滑块。而且既然你已经有了源代码,你完全可以移除那个讨厌的 Z 形块,没人喜欢它!

Solitaire

solitaire image

可惜,这不是纸牌游戏,而是一个基于“钉子”的游戏,你可以选择一块石头(o)并“跳过”相邻的石头进入洞中(.),并在这个过程中去掉你跳过的石头,最终只能在棋盘上留下一块石头,重复该过程直到棋盘被请空(只保留一个石头)。

如果你卡住了,有一个内置的解题器名为 M-x solitire-solve

Zone

我的另一个最爱。这是一个屏幕保护程序——或者更确切地说,是一系列的屏幕保护程序。

输入 M-x zone,然后看看屏幕上发生了什么!

你可以通过运行 M-x zone-when-idle(或从 elisp 调用它)来配置屏幕保护程序的空闲时间,时间以秒为单位。你也可以通过 M-x zone-leave-me-alone 来关闭它。

如果在你的同事看着的时候启动它,你的同事肯定会抓狂的。

乘法解谜

mpuz

这是另一个脑筋急转弯的益智游戏。当你运行 M-x mpuz 时,将看到一个乘法解谜题,你必须将字母替换为对应的数字,并确保数字相加(相乘?)符合结果。

如果遇到难题,可以运行 M-x mpuz-show-solution 来解决。

杂项

还有更多好玩的东西,但它们就不如刚才那些那么好玩好用了:

  • 你可以通过 M-x morse-regionM-x unmorse-region 将一个区域翻译成莫尔斯电码。
  • Dissociated Press 是一个非常简单的命令,它将一个类似随机穿行的马尔可夫链生成器应用到缓冲区中的文本中,并以此生成无意义的文本。试一下 M-x dissociated-press
  • gametree 软件包是一个通过电子邮件记录和跟踪国际象棋游戏的复杂方法。
  • M-x spook 命令插入随机单词(通常是到电子邮件中),目的是混淆/超载 “NSA 拖网渔船” —— 记住,这个模块可以追溯到 20 世纪 80 年代和 90 年代,那时应该有间谍们在监听各种单词。当然,即使是在十年前,这样做也会显得非常偏执和古怪,不过现在看来已经不那么奇怪了……

总结

我喜欢 Emacs 附带的游戏和玩具。它们大多来自于,嗯,我们姑且称之为一个不同的时代:一个允许或甚至鼓励奇思妙想的时代。有些玩意非常经典(如俄罗斯方块和汉诺塔),有些对经典游戏进行了有趣的变种(如黑盒)——但我很高兴这么多年后它们依然存在于 Emacs 中。我想知道时至今日,类似这些的玩意是否还会再纳入 Emacs 的代码库中;嗯,它们很可能不会——它们将被归入包管理仓库中,而在这个干净而无菌的世界中,它们无疑属于包管理仓库。

Emacs 要求将对 Emacs 体验不重要的内容转移到包管理仓库 ELPA 中。我的意思是,作为一个开发者,这是有道理的,但是……对于每一个被移出并流放到 ELPA 的包,我们是不是在蚕食 Emacs 的精髓?


via: https://www.masteringemacs.org/article/fun-games-in-emacs

作者:Mickey Petersen 选题:lujun9972 译者:lujun9972 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

常用开源工具的省时快捷方式。

我经常使用 SSH。我发现自己每天都要登录多个服务器和树莓派(与我位于同一房间,并接入互联网)。我有许多设备需要访问,并且获得访问权限的要求也不同,因此,除了使用各种 ssh / scp 命令选项之外,我还必须维护一个包含所有连接详细信息的配置文件。

随着时间的推移,我发现了一些省时的技巧和工具,你可能也会发现它们有用。

SSH 密钥

SSH 密钥是一种在不使用密码的情况下认证 SSH 连接的方法,可以用来加快访问速度或作为一种安全措施(如果你关闭了密码访问权限并确保仅允许授权的密钥)。要创建 SSH 密钥,请运行以下命令:

$ ssh-keygen

这将在 ~/.ssh/ 中创建一个密钥对(公钥和私钥)。将私钥(id_rsa)保留在 PC 上,切勿共享。你可以与其他人共享公钥(id_rsa.pub)或将其放置在其他服务器上。

ssh-copy-id

如果我在家中或公司工作时使用树莓派,则倾向于将 SSH 设置保留为默认设置,因为我不担心内部信任网络上的安全性,并且通常将 SSH 密钥(公钥)复制到树莓派上,以避免每次都使用密码进行身份验证。为此,我使用 ssh-copy-id 命令将其复制到树莓派。这会自动将你的密钥(公钥)添加到树莓派:

$ ssh-copy-id [email protected]

在生产服务器上,我倾向于关闭密码身份验证,仅允许授权的 SSH 密钥登录。

ssh-import-id

另一个类似的工具是 ssh-import-id。你可以使用此方法通过从 GitHub 导入密钥来授予你自己(或其他人)对计算机或服务器的访问权限。例如,我已经在我的 GitHub 帐户中注册了各个 SSH 密钥,因此无需密码即可推送到 GitHub。这些公钥是有效的,因此 ssh-import-id 可以使用它们在我的任何计算机上授权我:

$ ssh-import-id gh:bennuttall

我还可以使用它来授予其他人访问服务器的权限,而无需询问他们的密钥:

$ ssh-import-id gh:waveform80

storm

我还使用了名为 Storm 的工具,该工具可帮助你将 SSH 连接添加到 SSH 配置中,因此你不必记住这些连接细节信息。你可以使用 pip 安装它:

$ sudo pip3 install stormssh

然后,你可以使用以下命令将 SSH 连接信息添加到配置中:

$ storm add pi3 [email protected]

然后,你可以只使用 ssh pi3 来获得访问权限。类似的还有 scp file.txt pi3:sshfs pi pi3:

你还可以使用更多的 SSH 选项,例如端口号:

$ storm add pi3 [email protected]:2000

你可以参考 Storm 的文档轻松列出、搜索和编辑已保存的连接。Storm 实际所做的只是管理 SSH 配置文件 ~/.ssh/config 中的项目。一旦了解了它们是如何存储的,你就可以选择手动编辑它们。配置中的示例连接如下所示:

Host pi3
   user pi
   hostname 192.168.1.20
   port 22

结论

从树莓派到大型的云基础设施,SSH 是系统管理的重要工具。熟悉密钥管理会很方便。你还有其他 SSH 技巧要添加吗?我希望你在评论中分享他们。


via: https://opensource.com/article/20/2/ssh-tools

作者:Ben Nuttall 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

我作为一位世界电影和地区电影爱好者已经几十年了。这期间字幕是一个必不可少的工具,它可以使我享受来自不同国家不同语言的优秀电影。

如果你喜欢观看带有字幕的电影,你可能会注意到有时字幕并不同步或者说并不正确。

你知道你可以自己编写字幕并使得它们更完美吗?让我们向你展示一些 Linux 中的基本字幕编辑吧。

Editing subtitles in Linux

从闭路字幕数据中提取字幕

大概在 2012、2013 年我开始了解到有一款叫做 CCEextractor 的工具。随着时间的推移,它已经成为我必不可少的工具之一,尤其是当我偶然发现一份内含有字幕的媒体文件。

CCExtractor 负责解析视频文件以及从 闭路字幕 closed captions 数据中产生独立的字幕文件。

CCExtractor 是一个跨平台的、自由开源工具。自它形成的那年起该工具已经成熟了不少而如今已成为 GSOC 和谷歌编码输入的一部分。

简单来说,这个工具基本上是一系列脚本,这些脚本以一种顺序方式一个接着一个地给你提供提取到的字幕。

你可以按照本页的 CCExtractor 安装指南进行操作。

若安装后你想从媒体文件中提取字幕,请按以下步骤操作:

ccextractor <path_to_video_file>

该命令将会输出以下内容:

$ ccextractor $something.mkv
CCExtractor 0.87, Carlos Fernandez Sanz, Volker Quetschke.
Teletext portions taken from Petr Kutalek's telxcc
--------------------------------------------------------------------------
Input: $something.mkv
[Extract: 1] [Stream mode: Autodetect]
[Program : Auto ] [Hauppage mode: No] [Use MythTV code: Auto]
[Timing mode: Auto] [Debug: No] [Buffer input: No]
[Use pic_order_cnt_lsb for H.264: No] [Print CC decoder traces: No]
[Target format: .srt] [Encoding: UTF-8] [Delay: 0] [Trim lines: No]
[Add font color data: Yes] [Add font typesetting: Yes]
[Convert case: No] [Video-edit join: No]
[Extraction start time: not set (from start)]
[Extraction end time: not set (to end)]
[Live stream: No] [Clock frequency: 90000]
[Teletext page: Autodetect]
[Start credits text: None]
[Quantisation-mode: CCExtractor's internal function]
-----------------------------------------------------------------
Opening file: $something.mkv
File seems to be a Matroska/WebM container
Analyzing data in Matroska mode

Document type: matroska
Timecode scale: 1000000
Muxing app: libebml v1.3.1 + libmatroska v1.4.2
Writing app: mkvmerge v8.2.0 ('World of Adventure') 64bit
Title: $something

Track entry:
    Track number: 1
    UID: 1
    Type: video
    Codec ID: V_MPEG4/ISO/AVC
    Language: mal
    Name: $something
    
Track entry:
    Track number: 2
    UID: 2
    Type: audio
    Codec ID: A_MPEG/L3
    Language: mal
    Name: $something

Track entry:
    Track number: 3
    UID: somenumber
    Type: subtitle
    Codec ID: S_TEXT/UTF8
    Name: $something
 99%  |  144:34
100%  |  144:34
Output file: $something_eng.srt
Done, processing time = 6 seconds
Issues? Open a ticket here
https://github.com/CCExtractor/ccextractor/issues

它会大致浏览媒体文件。在这个例子中,它发现该媒体文件是马拉雅拉姆语言(mal)并且格式是 .mkv。之后它将字幕文件提取出来,命名为源文件名并添加“\_eng”后缀。

CCExtractor 是一款用来增强字幕功能和字幕编辑的优秀工具,我将在下一部分对它进行介绍。

趣味阅读:在 vicaps 有一份有趣的字幕提要,它讲解和分享为何字幕对我们如此重要。对于那些对这类话题感兴趣的人来说,这里面也有许多电影制作的细节。

用 SubtitleEditor 工具编辑字幕

你大概意识到大多数的字幕都是 .srt 格式 的。这种格式的优点在于你可以将它加载到文本编辑器中并对它进行少量的修改。

当进入一个简单的文本编辑器时,一个 srt 文件看起来会是这个样子:

1
00:00:00,959 --&gt; 00:00:13,744
"THE CABINET
OF DR. CALIGARI"

2
00:00:40,084 --&gt; 00:01:02,088
A TALE of the modern re-appearance of an 11th Century Myth
involting the strange and mysterious influence
of a mountebank monk over a somnambulist.

我分享的节选字幕来自于一部非常老的德国电影《卡里加里博士的小屋》(1920)。

Subtitleeditor 是一款非常棒的字幕编辑软件。字幕编辑器可以用来设置字幕持续时间、与多媒体文件同步的字幕帧率以及字幕间隔时间等等。接下来我将在这分享一些基本的字幕编辑。

首先,以安装 ccextractor 工具同样的方式安装 subtitleeditor 工具,使用你自己喜爱的安装方式。在 Debian 中,你可以使用命令:

sudo apt install subtitleeditor

当你安装完成后,让我们来看一下在你编辑字幕时一些常见的场景。

调整帧率使其媒体文件同步

如果你发现字幕与视频不同步,一个原因可能是视频文件的帧率与字幕文件的帧率并不一致。

你如何得知这些文件的帧率呢,然后呢?为了获取视频文件的帧率,你可以使用 mediainfo 工具。首先你可能需要发行版的包管理器来安装它。

使用 mediainfo 非常简单:

$ mediainfo somefile.mkv | grep Frame
 Format settings                          : CABAC / 4 Ref Frames
 Format settings, ReFrames                : 4 frames
 Frame rate mode                          : Constant
 Frame rate                               : 25.000 FPS
 Bits/(Pixel*Frame)                       : 0.082
 Frame rate                               : 46.875 FPS (1024 SPF)

现在你可以看到视频文件的帧率是 25.000 FPS 。我们看到的另一个帧率则是音频文件的帧率。虽然我可以分享为何在视频解码和音频解码等地方会使用特定的 fps,但这将会是一个不同的主题,与它相关的历史信息有很多。

下一个问题是解决字幕文件的帧率,这个稍微有点复杂。

通常情况下,大多数字幕都是压缩格式的。将.zip 归档文件和字幕文件(以 XXX.srt 结尾)一起解压缩。除此之外,通常还会有一个同名的 .info 文件,该文件可能包含字幕的帧率。

如果不是,那么通常最好去某个站点并从具有该帧速率信息的站点下载字幕。对于这个特定的德文文件,我使用 Opensubtitle.org 来找到它。

正如你在链接中所看到的,字幕的帧率是 23.976 FPS 。很明显,它不能与帧率为 25.000 FPS 的视频文件一起很好地播放。

在这种情况下,你可以使用字幕编辑工具来改变字幕文件的帧率。

按下 CTRL+A 选择字幕文件中的全部内容。点击 “Timings -> Change Framerate” ,将 23.976 fps 改为 25.000 fps 或者你想要的其他帧率,保存已更改的文件。

synchronize frame rates of subtitles in Linux

改变字幕文件的起点

有时以上的方法就足够解决问题了,但有时候以上方法并不足够解决问题。

在帧率相同时,你可能会发现字幕文件的开头与电影或媒体文件中起点并不相同。

在这种情况下,请按以下步骤进行操作:

按下 CTRL+A 键选中字幕文件的全部内容。点击 “Timings -> Select Move Subtitle” 。

Move subtitles using Subtitle Editor on Linux

设定字幕文件的新起点,保存已更改的文件。

Move subtitles using Subtitle Editor in Linux

如果你想要时间更精确一点,那么可以使用 mpv 来查看电影或者媒体文件并点击进度条(可以显示电影或者媒体文件的播放进度),它也会显示微秒。

通常我喜欢精准无误的操作,因此我会试着尽可能地仔细调节。相较于人类的反应时间来说,MPV 中的反应时间很精确。如果我想要极其精确的时间,那么我可以使用像 Audacity 之类的东西,但是那是另一种工具,你可以在上面做更多的事情。那也将会是我未来博客中将要探讨的东西。

调整字幕间隔时间

有时,两种方法都采用了还不够,甚至你可能需要缩短或增加间隔时间以使其与媒体文件同步。这是较为繁琐的工作之一,因为你必须单独确定每个句子的间隔时间。尤其是在媒体文件中帧率可变的情况下(现已很少见,但你仍然会得到此类文件)

在这种设想下,你可能因为无法实现自动编辑而不得不手动的修改间隔时间。最好的方式是修改视频文件(会降低视频质量)或者换另一个更高质量的片源,用你喜欢的设置对它进行转码 。这又是一重大任务,以后我会在我的一些博客文章上阐明。

总结

以上我分享的内容或多或少是对现有字幕文件的改进。如果从头开始,你需要花费大量的时间。我完全没有分享这一点,因为一部电影或一个小时内的任何视频材料都可以轻易地花费 4-6 个小时,甚至更多的时间,这取决于字幕员的技巧、耐心、上下文、行话、口音、是否是以英语为母语的人、翻译等,所有的这些都会对字幕的质量产生影响。

我希望自此以后你会觉得这件事很有趣,并将你的字幕处理的更好一点。如果你有其他想要补充的问题,请在下方留言。


via: https://itsfoss.com/editing-subtitles

作者:Shirish 选题:lujun9972 译者:chenmu-kk 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出