第一讲:吟诗作句
第一讲:吟诗作句
第一部分:形式语言学初步
Python语言对于想步入语言学专业的人来说应当是首选。理由就不用我多说了。从今天起,我们看看我们如何通过Python语言理解语言学理论。我这里所说的语言学理论,大致对应形式语言学的理论,这个理论又是生成语法的理论基础。但是这里不讲原理,只是用Python这门计算机语言做一些汉语的语言游戏,从中理解语言学理论。
1. 词汇表
我们知道,无论什么语言都可以从最复杂的文本进行分析最终得到其基本元素。例如汉语的基本元素就是汉字。所以,从形式上看,任何汉语文本都是由汉字构成的。这些汉字的总和就构成了语言的基本要素之一——词汇表,词汇表在语言学中又称「词库」,因此我们这里也称「词库」。
按照生成语法的定义,语言就是句子的集合。而句子,则是从词库中抽取一定数量的基本元素按照一定的语法规则排列后的结果。
上面的陈述如果用集合表示的话就是:
词库 = {汉字1, 汉字2,...汉字n}
字符串 ⊂ 词库
句子 = 语法(字符串)
对这个词库,我们知道它的长度是n,或者说,这个词库有n个汉字。有了词库,我们就可以从中抽取若干汉字,按照一定语法规则组成句子。不过,汉语的语法规则很不确定,我们现在不加以考虑,而是利用汉字的表意性,组成随机的‘句子’。这里,我们设定一个汉语的‘微环境’,词库仅由20个汉字组成:
词库 = {汉字1, 汉字2,...汉字20}
字符串 ⊂ 词库
语法: {字符串} → {字符串:长度(字符串)=5}
句子 = 语法(字符串)
这个语言的词库一共有20个元素;
从词库选取任意个字符组成字符串,字符串必须是词库的真子集(proper subset);
语法规则是一个函数:函数自变量是字符串,因变量是长度为5的字符串;
句子是由语法规则生成的字符串(长度为5的字符串);
这里唯一的语法规则就是:每个句子由5个汉字组成。此时这20个汉字的选择就大有学问。好在中国的诗词浩如烟海,其中有一些是回文诗,这些诗,正读、反读、甚至随机抽取读都可以得到有意义的‘诗句’。我这里选了这样一首:
落雪飞芳树,
幽红雨淡霞。
薄月迷香雾,
流风舞艳花。
如果把这首诗反读:
花艳舞风流
雾香迷月薄
霞淡雨红幽
树芳飞雪落
这首五言正好20个字,我们用来作为我们的词库。
词库 = ['落', '雪', '飞', '芳', '树', '幽', '红', '雨', '淡', '霞', '薄', '月', '迷', '香', '雾', '流', '风', '舞', '艳', '花']
这里,我们稍稍改变了一下词库的表达形式:首先,词库用方括号而不是花括号,第二,每个汉字都用单引号括起,表示汉字的属性是字符,每个字符都用逗号隔开。
2. 字符串
语言学中,我们把有限长的汉字组成的固定搭配称作‘词’,‘词组’,‘短语’,‘句子’,但是汉语语法很麻烦,有时我们也分不清这些术语之间的界限。为了省去这些麻烦,我们用一个通用的术语:【字符串】——表示所有可以当做‘词’,‘词组’,‘短语’,‘句子’的汉字组合。
字符串在语言学中的形式定义就是:0个或有限多个字符(symbol)的序列。
这里一一解释:
0个或有限多个:这个意思的形式表达是:0+;0个的意思是,不含任何字符,用‘’表达,又称作【空字符串】。‘有限多个’的意思是,存在一个自然数,使得这个字符串的个数可以用这个自然数表达。例如,我们这个‘微环境’中的句子——字符串的长度为自然数5,因此字符串中的字符是有限多个。
字符:这里‘字符’的意思是:从词库得到的基本元素,亦即,任何字符都是词库中的元素之一;设字符为s,则有:s ∈ 词库,字符不可以是词库以外的任何对象。
序列:由0+个字符由左向右顺序排列,如果两个序列元素相同、元素个数相同但排列顺序不同则被视为“不同”序列。简单的例子就是:“我打你”≠“你打我”。
有了字符串的概念,我们就可以对其做一些“运算”或者“操作”。
[字符串长度]
首先,每个字符串都有一定的长度,也就是上面我们所说的那个自然数n。例如:‘树芳飞雪落’的长度是5,我们用英语length的简写len表示:
len(‘树芳飞雪落’) = 5
换句话说,表达式‘len(‘树芳飞雪落’) ’就是自然数5的另一种写法,二者等价。例如:
3 + 5 = 8,就可以写成 3 + len(‘树芳飞雪落’) = 8
[联结]
两个字符串可以联结为一个更大的字符串,这个联结符号用加号‘+’,例如
‘啊’ + ‘呀’ = ‘啊呀’
很自然,
len(‘啊’) + len(’呀’) = len(‘啊呀’)
两个字符串连接后的长度等于各自长度之和。
[标量乘法]
一个字符串可以自我重复,这个操作称作标量乘法,用星号‘*’表示,后面的自然数表示重复的次数。等号‘=’的意思并非等于,而是经过左面的运算后得到的结果,这一点和数学等式不同。例如:
‘哈哈’ * 3 = ‘哈哈哈哈哈哈’
‘绿’ + ‘油' * 2 = ‘绿油油’
‘寻’ * 2 + ‘觅’ * 2 = ‘寻寻觅觅’
'西出阳关' + ‘无故人’ * 3 = ‘西出阳关无故人无故人无故人’ (阳关三叠)
和算术一样,混合运算乘法优先加法。
[索引]
每个字符串中的字符因为位置固定,所以为了抽取、引用方便,每个字符自左向右都赋予一个自然数,为了和后面的Python一致,这些自然数一律从0开始。字符索引的表示方法:
字符[自然数]
例如上面的字符串‘树芳飞雪落’,
树[0]
芳[1]
飞[2]
雪[3]
落[4]
但是这种方法有一个缺点,我们无法用一个统一方式引用某个特定字符。因此,在得到每个字符的索引之前,我们先要给‘树芳飞雪落’起个名字,然后用这个名字引用其中的每个字符。起名字的方法用等号表示:等号左面是名字,右面是字符串,
句 = ‘树芳飞雪落’
此时,‘句’就代表‘树芳飞雪落’了。所以,‘句’中所有字符都可以用索引引用了,例如:
句[0]表示‘树’,句[1]表示‘芳’,句[2]表示‘飞’,句[3]表示‘雪’,句[4]表示‘落’。这样,我们就可以灵活表示全‘句’,也可以表示字符串的某个部分:
例如:
句:‘树芳飞雪落’
句[2]:‘飞’
句[3] + 句[4] = ‘雪落’
句[4] + 句[3] = ‘落雪’
句[4] + 句[3] + 句[0] + 句[1] + 句[2] = ‘落雪树芳飞’
句[3] + 句[4] + 句[2] + 句[1] + 句[0] = ‘雪落飞芳树’
可以看出,索引是我们按照我们的愿望进行字符排列或重排列的主要手段。
3. Python基础
Python是一种非常人性化、易读易懂的计算机语言。这里,我们不会专门介绍语言本身,而是通过一些文字游戏的‘应用’来理解。如果你对前面关于‘词库/词汇表’和‘字符串’的内容完全理解,那么下面的内容对你应当不算难。
我这里假定你的设备中已经安装了python,对于如何在机器中安装python,网上有许多教程,这里不再重复。这里是”知乎“上的一个教程,可以参考。Python有两大版本,python 2和python 3。推荐用python 3。我的环境是Mac OS X terminal,这里用的编辑器是vi,而Python 3的版本是3.6。
如果是Windows,打开命令行界面,在「开始」|「运行」对话框中键入cmd回车后就会出现黑色对话框
C:\
如果是Linux或Mac,可以找到并运行terminal,出现对话框
$
要进入Python互动环境,在命令行键入python3:
Windows环境
C:\python3
或
Mac或Linux环境
$ python3
然后回车
此时会出现类似下列的信息:(Windows环境下略有不同)
Python 3.6.1 (default, Apr 4 2017, 09:40:21)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
1. 所有的字符串都应当用单引号或双引号括起,不管是空字符,一个字符还是多个字符。
例如:‘’,‘树’,‘绿油油’。如果你键入下列字符串,互动环境就会给出反馈表示你键入内容的【值】:
>>> ‘’
‘’
‘’的【值】就是‘’本身
>>> ‘树’
‘树’的【值】就是‘树’本身
2. 字符串可以起名,且名称可以任意。例如
>>> 句 = ‘树芳飞雪落’
此时互动环境没有反馈,表示起名成功
这时,如果键入:
>>> 句
‘树芳飞雪落’
表示<句>的值是‘树芳飞雪落’,我这里用<>表示名称,用‘’表示值,但实际上,名称没有任何标点符号。
注意:名称没有引号,也绝对不允许有引号。上面的表达式规定了一种格式:
名称-‘值’,每个名称必须有‘值’,这个值可以是空串‘’,但不能没有值;但是反过来,值可以没有名称。
3. 字符串操作运算
让我们用Python语言熟悉一下前面的内容:
(1) 字符串长度:
>>> len(‘树芳飞雪落’)
5
我们可以给这个表达式起个名字:长度
>>> 长度 = len(‘树芳飞雪落’)
此时,<长度>这个名称的值就是自然数5
>>> 长度
5
>>> 长度 + 5
10
(2) 标量乘法:
>>> ‘哈‘ * 5
‘哈哈哈哈哈’
>>> ‘绿’ + ‘油’ * 2
‘绿油油’
(3) 联结
五绝 = ‘落雪飞芳树’ + ‘幽红雨淡霞’ +‘薄月迷香雾’+‘流风舞艳花’
这里,<五绝>是名称,后面的4个字符串经‘联结’运算后,成为一个字符串
>>> 五绝
‘落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花’
这里需要注意的是,引号一定是英语的引号,不能是汉语的,否则会出错。如果你发现错误信息很有可能是引号出了毛病。
(4) 索引操作
索引操作是最重要的操作之一,因为我们之所以可以‘编辑’字符串,靠的就是索引操作。现在,我们可以利用<五绝>进行索引操作:
>>> 五绝[0]
‘落’
>>> 五绝[9]
‘露’
注意:在进行索引操作之前,最好先查看一下<五绝>的长度:
>>> len(五绝)
20
所以,我们可以使用的索引最大数字是19,如果超出19,互动环境就会报错。
>>> 五绝[20]
File "<stdin>", line 1, in <module>
IndexError: string index out of range
意思是,索引数字超过范围。同样要注意的是括号,一定是英语的括号,如果是汉语括号就会报错。
现在我们看一下索引的用法
>>> 句 = ‘树芳飞雪落’
>>> 句
‘树芳飞雪落’
>>> 句[2]
‘飞’
>>> 句[3] + 句[4]
‘雪落’
>>> 句[4] + 句[3]
‘落雪’
>>> 句[4] + 句[3] + 句[0] + 句[1] + 句[2]
‘落雪树芳飞’
>>> 句[4] + 句[3] + 句[2] + 句[1] + 句[0]
‘落雪飞芳树’
除了一个字一个字索引之外,我们还可以对字符串的某个部分索引,其操作符是冒号“:”(注意:是英语冒号)
>>> 句[2:4]
‘飞雪’
请注意这里的字数统计方法:从索引2开始(第三个字符),到索引4前面的的字符而不包括索引4。通常,得到字符串长度是结束索引数减开始索引数:4-2。
还需要注意的是,范围索引的索引号和上面单个索引略有不同,单索引号的最大数是字符串的长度减1,而范围索引中结束索引号的最大值是字符串长度本身。
如果省略结束索引号,则得到从开始索引号到后面所有字符
>>> 句[2:]
‘飞雪落’
如果省略开始索引号,则是从第一个字符开始(索引号0)到结束索引号的前一个字符
>>> 句[:4]
‘树芳飞雪’
如果开始索引号和结束索引号都省略,则得到整个字符串
>>> 句[:]
‘树芳飞雪落’
索引号还可以是负数,意思是倒数第n个,例如
>>> 句[-1]
‘落’
和正数一样,也可以有范围:
>>> 句[-3:-1]
‘飞雪’
从开始索引号后一个字符开始,到结束索引号的字符,所以上例是:从倒数第二个字符到倒数第一个。如果开始索引号大于结束索引号,则得到空字符串
>>> 句[-1:-3]
‘’
上面的索引都是字符串相邻,Python也允许字符串索引不连续,方法是,再加一个冒号,例如
‘树芳飞雪落’
>>> 句[0:5:2]
‘树飞落’
取索引号0—‘树’,索引号2—‘飞’,索引号4(索引号5前面的字符)—‘落’
其中’:2’的意思是每隔一个。上面的例子中索引号是从第一个到最后一个,所以也可以表示为:
>>> 句[::2]
我们知道[::]是取字符串中所有字符,‘:2’是每隔一个,所以效果和<句[0:5:2]>相同。
这里有一个最有用的索引方法请记住:利用负数作为隔开的度数
>>> 句[::-1]
取所有字符,每隔-1个,意思是,倒置字符的顺序:
‘落雪飞芳树’
总结:字符串有许多操作/运算方法,我们目前看到的就是:
1. 取长度:len(字符串)
2. 联结:字符串 + 字符串
3. 标量乘法:字符串 * n (n是自然数)
4. 索引操作:基本原理是利用字符串中每个字符的固定位置赋予一个从0开始的自然数。原理虽简单,但熟练使用仍需要练习才能掌握:包括开始索引号、结束索引号,间隔号。索引操作的本质是把字符编辑的操作转换成自然数的运算。
5. 命名:所有字符串都可以获得一个名称,方法是使用等号:=。有了名称,有些很长的字符串只要使用名称就可以引用。
第二部分:
这部分介绍Python的一些功能:
1. 随机数:
随机数是指,生成在一定范围内的任意数字。随机数非常有用,例如彩票就是随机数。除此之外,在计算机编程中,随机数可以帮助我们处理许多随机现象,例如模拟扑克牌洗牌。在我们的学习中,我们希望从一个词库中,任意抽取5个字符构成句子,因此也需要随机数。生成随机数需要许多技巧,但我们可以忽略这些细节,直接使用已经提供的现成方法。由于生成随机数不是Python互动环境标准配置,我们需要调用特定的、互动环境标准配置之外的程序。方法如下:
from random import randint
random是一个程序包,包含许多和随机数有关的程序,我们现在需要生成整数的程序,叫做randint,随机整数,所以上面的表达式就是:从程序包random中导入randint这个程序。
Python语言中,最小的独立程序称作【函数】,例如randint就是一个函数。不过不要将这里的【函数】与数学中的【函数】搞混。Python中的【函数】是指一段经常反复使用的代码,起个名字,加上一些可以控制函数状态的参数。关于【函数】我们待会会详细讨论,在这里我们只需要了解当我们使用Python编程时,大部分工作其实不需要我们自己搞定一切,相反,大部分编程的技术难度比较高的工作已经完成,以【函数】的形式供我们使用,我们只需把已经这些函数调用出来即可,其基本格式就是:
from 程序包 import 函数名
现在我们简要考察一下randint这个函数的格式:
1. 任何函数都有一个名称,当前这个函数的名称就是:randint;
2. 任何函数名后面一定要有一对圆括号——(),Python解释器就是根据名称后面有无圆括号来判断这个名称是否函数名;
3. 有些函数括号中包含参数,从形式上看,类似我们数学函数的自变量,但是在Python语言中,参数是为函数提供一些必要的初始值,例如randint有两个参数,表示随机数的范围:从最小值到最大值;
4. 大多数函数在完成任务后会把结果返回,使得用户在互动环境中键入了函数名(参数..)以后,解释器会把结果显示在屏幕上。如果函数没有返回值,那么这个函数会返回一个特殊的「值」——None。
有了randint这个函数,我们就可以生成一些随机数了。使用方法是:
randint(范围下限,范围上限),例如:
>>> randint(0, 100)
43
意思是:生成0-100之间的随机数。我们可以多执行几次:
>>> randint(0, 100)
4
>>> randint(0, 100)
15
你看,每次运行得到的结果都不同。有了随机数,我们就可以利用它干点事。上一部分我们为整个诗取名为<五绝>,再来看看:
>>> 五绝
'落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花'
取<五绝>的长度
>>> len(五绝)
20
这样,我们可以在0-20之间取随机数:
>>> start = randint(0, 15)
此时,名称start已经获得有randint返回的随机数,我们用这个值作为开始索引值,然后再取后面5个字符
之所以取值范围是0-15而不是0-20,是因为start只是指定了取值的起始位置,我们用start+5表示结尾的位置。如果我们的起始位置大于15,那么start+5就一定会大于20,超出了词库的最大长度,我们的程序就会出错。现在看一下start的随机数值:
>>> start
7
由于是随机数,每次都不一样,我这里恰好得到7。然后取start后面5个字符
>>> start + 5
12
所以,<五绝>中从start开始取5个字符就是:
>>> 五绝[start:start+5]
'雨淡霞薄月'
是不是挺美的?好!再取一个:
>>> start = randint(0, 15)
>>> 五绝[start:start+5]
'幽红雨淡霞'
当然,你如果正在和我在一起做,所得到的句子未必和我的相同,因为是‘随机数’啊。
2. 函数
好了,你已经会用Python‘吟诗作句’了。现在唯一的遗憾就是,每次我们完成任务无法保存非要从头开始再打一次字。有没有什么方法把我们做过的一切保存起来?当然有啊!基本方法就是建立函数。上面说了,Python中所谓的【函数】其实就是一小段程序,由于这段程序频繁被使用,所以为了避免每次都键入同样的内容,我们把这段程序单独抽出来,加上一个名称,并且把程序中所使用的一些初始值作为参数,使得在使用函数时,临时决定这些初始值的内容,这样函数就具有更大灵活性和适用范围。
现在我们就谈谈如何创建函数。首先,创建函数的标志是使用
def 函数名
这样的格式,其中,def是define的简称,亦即,定义一个函数。
让我们一步步来:
(1) 第一步:打开一个文本编辑器,如windows上的notepad,ubuntu linux上的Gedit,Mac OS X是TextEdit,当然如果你比较熟悉命令行,这些系统中都有vim,emacs等,这些编辑器使用难度比前面说的可能高一些,只要是可以编辑txt格式的,什么编辑器都可以。打开一个空白的文档,可以预先起个文件名,名称可以任意,但是扩展名必须是‘.py’,表示是Python的源文件,所以,文件名可以是:yinshi.py (吟诗),当然,你也可以用汉字作为文件名。
(2) 保存文件后,键入下列内容:
#!/usr/bin/env python3
五绝 = '落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花'
回文 = 五绝[::-1] # 回文是将原<五绝>字符顺序倒置
def 吟句():
"""
导入随机数生成函数,通过随机数,
从<五绝>中随机取五个连续字符,连字成诗
"""
from random import randint
length = len(五绝)
start = randint(0, length-5)
print(五绝[start:start+5])
"""
程序到此为止
"""
(3) 保存文件,退出。
下面说明一下:
第一行以“#!”开头,意思是指定Python的解释器为Python 3,和我们关系不大。如果你的环境是Windows,这行可以省略。如果你的环境是Linux或Mac,且只安装了Python 3而没有Python 2,这一句也可以省略。但通常,后两者都会预装Python 2。
第二行和第三行我们都见过,第二行是给诗句起名:<五绝>;第三句则是将该诗句中的所有字符顺序倒置。第三行中间以#隔开,后面的文字不属于程序语言,只作为说明文字。
第五行开始,我们有了一个新概念——函数。函数,正如我们上面所说,就是将经常反复使用的程序句子放在一起,在def后面给这些句子的集合起一个名字。我们这里的名称是<吟句>。函数的名称本质上和字符串的名称类似,都是给一串字符起名字。不过函数所代表的字符串是程序的句子,需要计算机理解,所以格式——语法更严格。因此第五行的意思是:
定义一个名称为<吟句>的函数,后面的括号是为了区别函数名称和非函数名称:凡函数名称后面必跟括号。
第六到第九行以三个双引号开头、三个双引号结尾,意思和#一样,之间的文字不是程序语言而是说明文,说明这个程序的功能和目的
第十行我们已经熟悉了,导入整数随机数生成函数——randint
第十一行是得到<五绝>的长度,并将这个数值取名为<length>
第十二行和前一行类似,从randint得到一个随机数,并命名为<start>
第十三行是新出现的,print,这里的意思是显示在命令行的屏幕上;print后面跟着的是这个动词的宾语:将<五绝>中从start开始到后面5个字符显示在屏幕上。
最后三行和前面一样,不是程序语言,而是说明文。至此,程序结束。
【注意事项】:
1. Python的语法:在所有以引号结尾的句子后的新句子,必须要缩进,亦即,每句须用tab键作为先导;
2. 为了让程序易懂,这里用了许多汉字作为程序的一部分,但是Python语言基本上是英语字符,因此,在打字时,时刻要注意字符间转换,特别是汉语状态和英语状态下许多形似的字符绝对不可以搞混,例如:引号(包括双引号和单引号)、括号(包括方括号和圆括号)、等号,所有这些字符必须是英语状态的字符。如果无法确定,可以先在互动环境中运行,将可以执行的句子复制粘贴到编辑器。
3. 另外需要注意的是,将文件保存到和启动python互动环境同一文件夹中,切记。
如果一切没有问题,现在可以回到互动环境,在命令行键入
% python3
>>> from yinshi import *
这里,我们需要导入我们的文件,但后面不必跟扩展名:.py。如果一切顺利,互动环境没有任何‘抱怨’,如果出现
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named ‘yinshi’
则说明你的文件放错地方了。需要查看你的‘yinshi.py’是否在当前文件夹。
如果因某种原因,程序包无法导入,那只能直接导入文件本身了
>>> exec(open(‘yinshi.py’).read())
这句话的意思是:打开名称为‘yinshi.py’的文件,然后将文件内容读出来并执行。这时,如果你的文件不在当前文件夹,需要加上路径名,另外,如果你的文件名和这里不一样,也需要重新确认。
如果一切顺利,那么现在我们就可以‘吟诗作句’了,反复键入(或者用上箭头键),每次都可以得到不同的诗句
>>> 吟句()
'雾迷雪芳雨'
>>> 吟句()
'月雪树红红'
>>> 吟句()
'薄雪雪迷红'
>>> 吟句()
'月流落芳花'
当然,你得到的‘诗句’和我的不同,你看,我们这个简单的程序已经可以‘吟诗做句’了,虽然有的诗句对于我们人类来说不那么自然,但是对于我们这个寥寥数行的程序做到这样的创造性已经不简单了,更重要的是,你已经从实践角度理解了形式语言学中关于字符串操作的一些基本原理。
第一部分:形式语言学初步
Python语言对于想步入语言学专业的人来说应当是首选。理由就不用我多说了。从今天起,我们看看我们如何通过Python语言理解语言学理论。我这里所说的语言学理论,大致对应形式语言学的理论,这个理论又是生成语法的理论基础。但是这里不讲原理,只是用Python这门计算机语言做一些汉语的语言游戏,从中理解语言学理论。
1. 词汇表
我们知道,无论什么语言都可以从最复杂的文本进行分析最终得到其基本元素。例如汉语的基本元素就是汉字。所以,从形式上看,任何汉语文本都是由汉字构成的。这些汉字的总和就构成了语言的基本要素之一——词汇表,词汇表在语言学中又称「词库」,因此我们这里也称「词库」。
按照生成语法的定义,语言就是句子的集合。而句子,则是从词库中抽取一定数量的基本元素按照一定的语法规则排列后的结果。
上面的陈述如果用集合表示的话就是:
词库 = {汉字1, 汉字2,...汉字n}
字符串 ⊂ 词库
句子 = 语法(字符串)
对这个词库,我们知道它的长度是n,或者说,这个词库有n个汉字。有了词库,我们就可以从中抽取若干汉字,按照一定语法规则组成句子。不过,汉语的语法规则很不确定,我们现在不加以考虑,而是利用汉字的表意性,组成随机的‘句子’。这里,我们设定一个汉语的‘微环境’,词库仅由20个汉字组成:
词库 = {汉字1, 汉字2,...汉字20}
字符串 ⊂ 词库
语法: {字符串} → {字符串:长度(字符串)=5}
句子 = 语法(字符串)
这个语言的词库一共有20个元素;
从词库选取任意个字符组成字符串,字符串必须是词库的真子集(proper subset);
语法规则是一个函数:函数自变量是字符串,因变量是长度为5的字符串;
句子是由语法规则生成的字符串(长度为5的字符串);
这里唯一的语法规则就是:每个句子由5个汉字组成。此时这20个汉字的选择就大有学问。好在中国的诗词浩如烟海,其中有一些是回文诗,这些诗,正读、反读、甚至随机抽取读都可以得到有意义的‘诗句’。我这里选了这样一首:
落雪飞芳树,
幽红雨淡霞。
薄月迷香雾,
流风舞艳花。
如果把这首诗反读:
花艳舞风流
雾香迷月薄
霞淡雨红幽
树芳飞雪落
这首五言正好20个字,我们用来作为我们的词库。
词库 = ['落', '雪', '飞', '芳', '树', '幽', '红', '雨', '淡', '霞', '薄', '月', '迷', '香', '雾', '流', '风', '舞', '艳', '花']
这里,我们稍稍改变了一下词库的表达形式:首先,词库用方括号而不是花括号,第二,每个汉字都用单引号括起,表示汉字的属性是字符,每个字符都用逗号隔开。
2. 字符串
语言学中,我们把有限长的汉字组成的固定搭配称作‘词’,‘词组’,‘短语’,‘句子’,但是汉语语法很麻烦,有时我们也分不清这些术语之间的界限。为了省去这些麻烦,我们用一个通用的术语:【字符串】——表示所有可以当做‘词’,‘词组’,‘短语’,‘句子’的汉字组合。
字符串在语言学中的形式定义就是:0个或有限多个字符(symbol)的序列。
这里一一解释:
0个或有限多个:这个意思的形式表达是:0+;0个的意思是,不含任何字符,用‘’表达,又称作【空字符串】。‘有限多个’的意思是,存在一个自然数,使得这个字符串的个数可以用这个自然数表达。例如,我们这个‘微环境’中的句子——字符串的长度为自然数5,因此字符串中的字符是有限多个。
字符:这里‘字符’的意思是:从词库得到的基本元素,亦即,任何字符都是词库中的元素之一;设字符为s,则有:s ∈ 词库,字符不可以是词库以外的任何对象。
序列:由0+个字符由左向右顺序排列,如果两个序列元素相同、元素个数相同但排列顺序不同则被视为“不同”序列。简单的例子就是:“我打你”≠“你打我”。
有了字符串的概念,我们就可以对其做一些“运算”或者“操作”。
[字符串长度]
首先,每个字符串都有一定的长度,也就是上面我们所说的那个自然数n。例如:‘树芳飞雪落’的长度是5,我们用英语length的简写len表示:
len(‘树芳飞雪落’) = 5
换句话说,表达式‘len(‘树芳飞雪落’) ’就是自然数5的另一种写法,二者等价。例如:
3 + 5 = 8,就可以写成 3 + len(‘树芳飞雪落’) = 8
[联结]
两个字符串可以联结为一个更大的字符串,这个联结符号用加号‘+’,例如
‘啊’ + ‘呀’ = ‘啊呀’
很自然,
len(‘啊’) + len(’呀’) = len(‘啊呀’)
两个字符串连接后的长度等于各自长度之和。
[标量乘法]
一个字符串可以自我重复,这个操作称作标量乘法,用星号‘*’表示,后面的自然数表示重复的次数。等号‘=’的意思并非等于,而是经过左面的运算后得到的结果,这一点和数学等式不同。例如:
‘哈哈’ * 3 = ‘哈哈哈哈哈哈’
‘绿’ + ‘油' * 2 = ‘绿油油’
‘寻’ * 2 + ‘觅’ * 2 = ‘寻寻觅觅’
'西出阳关' + ‘无故人’ * 3 = ‘西出阳关无故人无故人无故人’ (阳关三叠)
和算术一样,混合运算乘法优先加法。
[索引]
每个字符串中的字符因为位置固定,所以为了抽取、引用方便,每个字符自左向右都赋予一个自然数,为了和后面的Python一致,这些自然数一律从0开始。字符索引的表示方法:
字符[自然数]
例如上面的字符串‘树芳飞雪落’,
树[0]
芳[1]
飞[2]
雪[3]
落[4]
但是这种方法有一个缺点,我们无法用一个统一方式引用某个特定字符。因此,在得到每个字符的索引之前,我们先要给‘树芳飞雪落’起个名字,然后用这个名字引用其中的每个字符。起名字的方法用等号表示:等号左面是名字,右面是字符串,
句 = ‘树芳飞雪落’
此时,‘句’就代表‘树芳飞雪落’了。所以,‘句’中所有字符都可以用索引引用了,例如:
句[0]表示‘树’,句[1]表示‘芳’,句[2]表示‘飞’,句[3]表示‘雪’,句[4]表示‘落’。这样,我们就可以灵活表示全‘句’,也可以表示字符串的某个部分:
例如:
句:‘树芳飞雪落’
句[2]:‘飞’
句[3] + 句[4] = ‘雪落’
句[4] + 句[3] = ‘落雪’
句[4] + 句[3] + 句[0] + 句[1] + 句[2] = ‘落雪树芳飞’
句[3] + 句[4] + 句[2] + 句[1] + 句[0] = ‘雪落飞芳树’
可以看出,索引是我们按照我们的愿望进行字符排列或重排列的主要手段。
3. Python基础
Python是一种非常人性化、易读易懂的计算机语言。这里,我们不会专门介绍语言本身,而是通过一些文字游戏的‘应用’来理解。如果你对前面关于‘词库/词汇表’和‘字符串’的内容完全理解,那么下面的内容对你应当不算难。
我这里假定你的设备中已经安装了python,对于如何在机器中安装python,网上有许多教程,这里不再重复。这里是”知乎“上的一个教程,可以参考。Python有两大版本,python 2和python 3。推荐用python 3。我的环境是Mac OS X terminal,这里用的编辑器是vi,而Python 3的版本是3.6。
如果是Windows,打开命令行界面,在「开始」|「运行」对话框中键入cmd回车后就会出现黑色对话框
C:\
如果是Linux或Mac,可以找到并运行terminal,出现对话框
$
要进入Python互动环境,在命令行键入python3:
Windows环境
C:\python3
或
Mac或Linux环境
$ python3
然后回车
此时会出现类似下列的信息:(Windows环境下略有不同)
Python 3.6.1 (default, Apr 4 2017, 09:40:21)
[GCC 4.2.1 Compatible Apple LLVM 8.1.0 (clang-802.0.38)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>>
1. 所有的字符串都应当用单引号或双引号括起,不管是空字符,一个字符还是多个字符。
例如:‘’,‘树’,‘绿油油’。如果你键入下列字符串,互动环境就会给出反馈表示你键入内容的【值】:
>>> ‘’
‘’
‘’的【值】就是‘’本身
>>> ‘树’
‘树’的【值】就是‘树’本身
2. 字符串可以起名,且名称可以任意。例如
>>> 句 = ‘树芳飞雪落’
此时互动环境没有反馈,表示起名成功
这时,如果键入:
>>> 句
‘树芳飞雪落’
表示<句>的值是‘树芳飞雪落’,我这里用<>表示名称,用‘’表示值,但实际上,名称没有任何标点符号。
注意:名称没有引号,也绝对不允许有引号。上面的表达式规定了一种格式:
名称-‘值’,每个名称必须有‘值’,这个值可以是空串‘’,但不能没有值;但是反过来,值可以没有名称。
3. 字符串操作运算
让我们用Python语言熟悉一下前面的内容:
(1) 字符串长度:
>>> len(‘树芳飞雪落’)
5
我们可以给这个表达式起个名字:长度
>>> 长度 = len(‘树芳飞雪落’)
此时,<长度>这个名称的值就是自然数5
>>> 长度
5
>>> 长度 + 5
10
(2) 标量乘法:
>>> ‘哈‘ * 5
‘哈哈哈哈哈’
>>> ‘绿’ + ‘油’ * 2
‘绿油油’
(3) 联结
五绝 = ‘落雪飞芳树’ + ‘幽红雨淡霞’ +‘薄月迷香雾’+‘流风舞艳花’
这里,<五绝>是名称,后面的4个字符串经‘联结’运算后,成为一个字符串
>>> 五绝
‘落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花’
这里需要注意的是,引号一定是英语的引号,不能是汉语的,否则会出错。如果你发现错误信息很有可能是引号出了毛病。
(4) 索引操作
索引操作是最重要的操作之一,因为我们之所以可以‘编辑’字符串,靠的就是索引操作。现在,我们可以利用<五绝>进行索引操作:
>>> 五绝[0]
‘落’
>>> 五绝[9]
‘露’
注意:在进行索引操作之前,最好先查看一下<五绝>的长度:
>>> len(五绝)
20
所以,我们可以使用的索引最大数字是19,如果超出19,互动环境就会报错。
>>> 五绝[20]
File "<stdin>", line 1, in <module>
IndexError: string index out of range
意思是,索引数字超过范围。同样要注意的是括号,一定是英语的括号,如果是汉语括号就会报错。
现在我们看一下索引的用法
>>> 句 = ‘树芳飞雪落’
>>> 句
‘树芳飞雪落’
>>> 句[2]
‘飞’
>>> 句[3] + 句[4]
‘雪落’
>>> 句[4] + 句[3]
‘落雪’
>>> 句[4] + 句[3] + 句[0] + 句[1] + 句[2]
‘落雪树芳飞’
>>> 句[4] + 句[3] + 句[2] + 句[1] + 句[0]
‘落雪飞芳树’
除了一个字一个字索引之外,我们还可以对字符串的某个部分索引,其操作符是冒号“:”(注意:是英语冒号)
>>> 句[2:4]
‘飞雪’
请注意这里的字数统计方法:从索引2开始(第三个字符),到索引4前面的的字符而不包括索引4。通常,得到字符串长度是结束索引数减开始索引数:4-2。
还需要注意的是,范围索引的索引号和上面单个索引略有不同,单索引号的最大数是字符串的长度减1,而范围索引中结束索引号的最大值是字符串长度本身。
如果省略结束索引号,则得到从开始索引号到后面所有字符
>>> 句[2:]
‘飞雪落’
如果省略开始索引号,则是从第一个字符开始(索引号0)到结束索引号的前一个字符
>>> 句[:4]
‘树芳飞雪’
如果开始索引号和结束索引号都省略,则得到整个字符串
>>> 句[:]
‘树芳飞雪落’
索引号还可以是负数,意思是倒数第n个,例如
>>> 句[-1]
‘落’
和正数一样,也可以有范围:
>>> 句[-3:-1]
‘飞雪’
从开始索引号后一个字符开始,到结束索引号的字符,所以上例是:从倒数第二个字符到倒数第一个。如果开始索引号大于结束索引号,则得到空字符串
>>> 句[-1:-3]
‘’
上面的索引都是字符串相邻,Python也允许字符串索引不连续,方法是,再加一个冒号,例如
‘树芳飞雪落’
>>> 句[0:5:2]
‘树飞落’
取索引号0—‘树’,索引号2—‘飞’,索引号4(索引号5前面的字符)—‘落’
其中’:2’的意思是每隔一个。上面的例子中索引号是从第一个到最后一个,所以也可以表示为:
>>> 句[::2]
我们知道[::]是取字符串中所有字符,‘:2’是每隔一个,所以效果和<句[0:5:2]>相同。
这里有一个最有用的索引方法请记住:利用负数作为隔开的度数
>>> 句[::-1]
取所有字符,每隔-1个,意思是,倒置字符的顺序:
‘落雪飞芳树’
总结:字符串有许多操作/运算方法,我们目前看到的就是:
1. 取长度:len(字符串)
2. 联结:字符串 + 字符串
3. 标量乘法:字符串 * n (n是自然数)
4. 索引操作:基本原理是利用字符串中每个字符的固定位置赋予一个从0开始的自然数。原理虽简单,但熟练使用仍需要练习才能掌握:包括开始索引号、结束索引号,间隔号。索引操作的本质是把字符编辑的操作转换成自然数的运算。
5. 命名:所有字符串都可以获得一个名称,方法是使用等号:=。有了名称,有些很长的字符串只要使用名称就可以引用。
第二部分:
这部分介绍Python的一些功能:
1. 随机数:
随机数是指,生成在一定范围内的任意数字。随机数非常有用,例如彩票就是随机数。除此之外,在计算机编程中,随机数可以帮助我们处理许多随机现象,例如模拟扑克牌洗牌。在我们的学习中,我们希望从一个词库中,任意抽取5个字符构成句子,因此也需要随机数。生成随机数需要许多技巧,但我们可以忽略这些细节,直接使用已经提供的现成方法。由于生成随机数不是Python互动环境标准配置,我们需要调用特定的、互动环境标准配置之外的程序。方法如下:
from random import randint
random是一个程序包,包含许多和随机数有关的程序,我们现在需要生成整数的程序,叫做randint,随机整数,所以上面的表达式就是:从程序包random中导入randint这个程序。
Python语言中,最小的独立程序称作【函数】,例如randint就是一个函数。不过不要将这里的【函数】与数学中的【函数】搞混。Python中的【函数】是指一段经常反复使用的代码,起个名字,加上一些可以控制函数状态的参数。关于【函数】我们待会会详细讨论,在这里我们只需要了解当我们使用Python编程时,大部分工作其实不需要我们自己搞定一切,相反,大部分编程的技术难度比较高的工作已经完成,以【函数】的形式供我们使用,我们只需把已经这些函数调用出来即可,其基本格式就是:
from 程序包 import 函数名
现在我们简要考察一下randint这个函数的格式:
1. 任何函数都有一个名称,当前这个函数的名称就是:randint;
2. 任何函数名后面一定要有一对圆括号——(),Python解释器就是根据名称后面有无圆括号来判断这个名称是否函数名;
3. 有些函数括号中包含参数,从形式上看,类似我们数学函数的自变量,但是在Python语言中,参数是为函数提供一些必要的初始值,例如randint有两个参数,表示随机数的范围:从最小值到最大值;
4. 大多数函数在完成任务后会把结果返回,使得用户在互动环境中键入了函数名(参数..)以后,解释器会把结果显示在屏幕上。如果函数没有返回值,那么这个函数会返回一个特殊的「值」——None。
有了randint这个函数,我们就可以生成一些随机数了。使用方法是:
randint(范围下限,范围上限),例如:
>>> randint(0, 100)
43
意思是:生成0-100之间的随机数。我们可以多执行几次:
>>> randint(0, 100)
4
>>> randint(0, 100)
15
你看,每次运行得到的结果都不同。有了随机数,我们就可以利用它干点事。上一部分我们为整个诗取名为<五绝>,再来看看:
>>> 五绝
'落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花'
取<五绝>的长度
>>> len(五绝)
20
这样,我们可以在0-20之间取随机数:
>>> start = randint(0, 15)
此时,名称start已经获得有randint返回的随机数,我们用这个值作为开始索引值,然后再取后面5个字符
之所以取值范围是0-15而不是0-20,是因为start只是指定了取值的起始位置,我们用start+5表示结尾的位置。如果我们的起始位置大于15,那么start+5就一定会大于20,超出了词库的最大长度,我们的程序就会出错。现在看一下start的随机数值:
>>> start
7
由于是随机数,每次都不一样,我这里恰好得到7。然后取start后面5个字符
>>> start + 5
12
所以,<五绝>中从start开始取5个字符就是:
>>> 五绝[start:start+5]
'雨淡霞薄月'
是不是挺美的?好!再取一个:
>>> start = randint(0, 15)
>>> 五绝[start:start+5]
'幽红雨淡霞'
当然,你如果正在和我在一起做,所得到的句子未必和我的相同,因为是‘随机数’啊。
2. 函数
好了,你已经会用Python‘吟诗作句’了。现在唯一的遗憾就是,每次我们完成任务无法保存非要从头开始再打一次字。有没有什么方法把我们做过的一切保存起来?当然有啊!基本方法就是建立函数。上面说了,Python中所谓的【函数】其实就是一小段程序,由于这段程序频繁被使用,所以为了避免每次都键入同样的内容,我们把这段程序单独抽出来,加上一个名称,并且把程序中所使用的一些初始值作为参数,使得在使用函数时,临时决定这些初始值的内容,这样函数就具有更大灵活性和适用范围。
现在我们就谈谈如何创建函数。首先,创建函数的标志是使用
def 函数名
这样的格式,其中,def是define的简称,亦即,定义一个函数。
让我们一步步来:
(1) 第一步:打开一个文本编辑器,如windows上的notepad,ubuntu linux上的Gedit,Mac OS X是TextEdit,当然如果你比较熟悉命令行,这些系统中都有vim,emacs等,这些编辑器使用难度比前面说的可能高一些,只要是可以编辑txt格式的,什么编辑器都可以。打开一个空白的文档,可以预先起个文件名,名称可以任意,但是扩展名必须是‘.py’,表示是Python的源文件,所以,文件名可以是:yinshi.py (吟诗),当然,你也可以用汉字作为文件名。
(2) 保存文件后,键入下列内容:
#!/usr/bin/env python3
五绝 = '落雪飞芳树幽红雨淡霞薄月迷香雾流风舞艳花'
回文 = 五绝[::-1] # 回文是将原<五绝>字符顺序倒置
def 吟句():
"""
导入随机数生成函数,通过随机数,
从<五绝>中随机取五个连续字符,连字成诗
"""
from random import randint
length = len(五绝)
start = randint(0, length-5)
print(五绝[start:start+5])
"""
程序到此为止
"""
(3) 保存文件,退出。
下面说明一下:
第一行以“#!”开头,意思是指定Python的解释器为Python 3,和我们关系不大。如果你的环境是Windows,这行可以省略。如果你的环境是Linux或Mac,且只安装了Python 3而没有Python 2,这一句也可以省略。但通常,后两者都会预装Python 2。
第二行和第三行我们都见过,第二行是给诗句起名:<五绝>;第三句则是将该诗句中的所有字符顺序倒置。第三行中间以#隔开,后面的文字不属于程序语言,只作为说明文字。
第五行开始,我们有了一个新概念——函数。函数,正如我们上面所说,就是将经常反复使用的程序句子放在一起,在def后面给这些句子的集合起一个名字。我们这里的名称是<吟句>。函数的名称本质上和字符串的名称类似,都是给一串字符起名字。不过函数所代表的字符串是程序的句子,需要计算机理解,所以格式——语法更严格。因此第五行的意思是:
定义一个名称为<吟句>的函数,后面的括号是为了区别函数名称和非函数名称:凡函数名称后面必跟括号。
第六到第九行以三个双引号开头、三个双引号结尾,意思和#一样,之间的文字不是程序语言而是说明文,说明这个程序的功能和目的
第十行我们已经熟悉了,导入整数随机数生成函数——randint
第十一行是得到<五绝>的长度,并将这个数值取名为<length>
第十二行和前一行类似,从randint得到一个随机数,并命名为<start>
第十三行是新出现的,print,这里的意思是显示在命令行的屏幕上;print后面跟着的是这个动词的宾语:将<五绝>中从start开始到后面5个字符显示在屏幕上。
最后三行和前面一样,不是程序语言,而是说明文。至此,程序结束。
【注意事项】:
1. Python的语法:在所有以引号结尾的句子后的新句子,必须要缩进,亦即,每句须用tab键作为先导;
2. 为了让程序易懂,这里用了许多汉字作为程序的一部分,但是Python语言基本上是英语字符,因此,在打字时,时刻要注意字符间转换,特别是汉语状态和英语状态下许多形似的字符绝对不可以搞混,例如:引号(包括双引号和单引号)、括号(包括方括号和圆括号)、等号,所有这些字符必须是英语状态的字符。如果无法确定,可以先在互动环境中运行,将可以执行的句子复制粘贴到编辑器。
3. 另外需要注意的是,将文件保存到和启动python互动环境同一文件夹中,切记。
如果一切没有问题,现在可以回到互动环境,在命令行键入
% python3
>>> from yinshi import *
这里,我们需要导入我们的文件,但后面不必跟扩展名:.py。如果一切顺利,互动环境没有任何‘抱怨’,如果出现
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ModuleNotFoundError: No module named ‘yinshi’
则说明你的文件放错地方了。需要查看你的‘yinshi.py’是否在当前文件夹。
如果因某种原因,程序包无法导入,那只能直接导入文件本身了
>>> exec(open(‘yinshi.py’).read())
这句话的意思是:打开名称为‘yinshi.py’的文件,然后将文件内容读出来并执行。这时,如果你的文件不在当前文件夹,需要加上路径名,另外,如果你的文件名和这里不一样,也需要重新确认。
如果一切顺利,那么现在我们就可以‘吟诗作句’了,反复键入(或者用上箭头键),每次都可以得到不同的诗句
>>> 吟句()
'雾迷雪芳雨'
>>> 吟句()
'月雪树红红'
>>> 吟句()
'薄雪雪迷红'
>>> 吟句()
'月流落芳花'
当然,你得到的‘诗句’和我的不同,你看,我们这个简单的程序已经可以‘吟诗做句’了,虽然有的诗句对于我们人类来说不那么自然,但是对于我们这个寥寥数行的程序做到这样的创造性已经不简单了,更重要的是,你已经从实践角度理解了形式语言学中关于字符串操作的一些基本原理。
> 我来回应