字符串和数据结构
1.字符编码
要了解字符串就必要了解字符编码问题,因为计算机只能处理数字所以要是利用计算机处理文本,就必要把文本转换成数字才能处理。计算机设计时将8bit(比特)作为一个byte(字节),所以一个字节能表示的最大整数就是255(二进制11111111等于十进制255),如果要使用更大的整数呢,那就要用更多的字节。理解就是2^8n-1且(n>=1),当n=4时能表示的最大整数是4294967295。
计算机是没国人发明的,嗖,最早只有127个字符被编码到计算机里,也就是我们常用的键盘上的大小写字母、数字和一些符号。这个编码表被称为ASCII编码,比如大小写字母A的编码是65。
这就会有一个问题,要是除了美国人以外的人要用计算机怎么办呢?一个中文占用两个字节,还不能和ASCII编码冲突,所以一个中国制定了GB2313编码,用来编码中文。日本把日文编到Shift_JIS里,韩国把韩文编到Euc-kr里,在多种语言里难免会出现冲突,甚至还会出现乱码。
因此Unicode出生了,该编码方式把所有的语言统一到一套编码里。在Unicode里通尝用两个字节表示一个字符(如果遇到生僻字,还需要4个字节)。现在操作系统和大多编程语言都支持Unicode。
ASCII编码和Unicode的区别在于前者使用1个字节,而后者通常使用两个字节。
字母A用ASCII编码是十进制的65,二进制是010000001;
汉字中已经超出了一个字节的范围,用Unicode编码的十进制是20013,二进制01001110 00101101。那字符A在Unicode中表示00000000 01000001,只需要在前面补零就可以了。
那我们经常接触的UTF-8编码又是怎么回事儿呢!如果全都统一使用Unicode的话就会出现一个新的问题,我要全都是使用英文的话一个字节就搞定了,如果使用Unicode就要比ASCII多一倍的存储空间,会影响存储和传输。所以UTF-8编码就是可变长编码,英文就是一个字节,汉字是3个字节,生僻字会被编码成4-6个字节,使用UTF-8能节省大量的空间。
字符 | ASCII | Unicode | UTF-8 |
---|---|---|---|
A | 01000001 | 00000000 01000001 | 01000001 |
中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
在表格中发现UTF-8的兼容性很强,可支持ASCII编码的历史软件。在计算机内存中统一使用Unicode编码,需要存储和传输时就转换成UTF-8编码。在电脑中打开记事本编辑的时候,从文件读取的UTF-8字符本转换成Unicode字符到内存,保存的时候再转换成UTF-8到硬盘。浏览网页的时候,服务器会把动态生成的Unicode内容换成UTF-8在传输到浏览器,大家可以使用开发者工具查看源码上会有< meta charset=“UTF-8”>的信息,表示该网页正在使用UTF-8编码。
2.字符串使用
计算机在最初设计的时候使用于导弹弹道的计算,那个时候基本都是处理数值型的信息。世界上第一台电子计算机每秒能完成约5000次浮点运算。但今天我们接触更多的是文本,要想熟练的处理这些文本就要连接字符串类型和跟它相关的知识。
字符串:由零个或多个字符组成的有限的序列,我们可以看成
在python里,如果我们把单个或者多个字符用引号引起来,就可以表示一个字符。
str1 = 'abcdefg'
str2 = "hello world!"
# 三个引号表示可以折行
str3 = """
abcdefg
hello world!
"""
可以在字符串使用 \ 来转义,在反斜杠后面的字符就不再是它原来的意义。换行符\n、制表符\t,可以尝试打印如下结果。
print('\'hi! how are you\'')
print('\n\\hello\\\n')
还可以在 \ 后面跟一个八进制或者十六进制的来表示字符,例如\141和\x61都代表小写字母a。前者是八进制的表示,后者是十六进制表示。也可以在 \ 后面跟Unicode字符编码来表示字符,\u6770\u7279代表中文杰特。如果我们不希望 \ 表示转义呢,可以通过在在字符串最前面加上字母r来加以说明。尝试打印下面的内容:
print('\u6770\u7279')
print('\141\142\143\x61\x62\x63')
print(r'\'hello, world!\'')
print(r'\n\\hello, world!\\\n')
python为我们使用字符串类型提供了非常丰富的运算符,我们可以使用+来实现字符串的拼接,可以使用*来重复一个字符串的内容,可以使用in和not in来判断一个字符串是否包含另一个字符串(成员)。也可以[]和[:]运算符取出某些字符(切片)。代码运行如下:
s = 'abcdefghijklmnopqrstuvwxyz!!!'
m = '123456'
print(s*3) # abcdefghijklmnopqrstuvwxyz!!!abcdefghijklmnopqrstuvwxyz!!!abcdefghijklmnopqrstuvwxyz!!!
print(s + m) # abcdefghijklmnopqrstuvwxyz!!!123456
print('s' in s) # True
print('bs' not in s) # True
print(s[5:]) # fghijklmnopqrstuvwxyz!!!
print(s[5::3]) # filorux!
print(s[::3]) # adgjmpsvy!
print(s[8::-1]) # ihgfedcba
print(s[-5::]) # yz!!!
print(s[-5::-1]) # yxwvutsrqponmlkjihgfedcba
在python里,有很多内置函数是处理字符串的,例:
str1 = 'xin shou lian xi123'
# 计算字符串长度
print(len(str1))
# 将首字母转换为大写打印
print(str1.captalize())
# 将每个单词首字母转换成大写打印
print(str1.title())
# 将小写转换成大写
print(str1.upper())
# 查找字符在字符串中的位置
print(str1.find('ia'))
print(str1.find('hel')) # 当所找字符串不存在时会返回-1,引发异常
# 检查字符串开头字符
print(str1.startswith('x')) # True
print(str1.startswith('X')) # False
# 检查字符串结尾
print(str1.endswith('xi')) # True
# 将字符串以指定的宽度居中并填充指定的字符
print(str1.center(100, '*'))
# 将字符串以指定宽度靠右并填充指定的字符
print(str1.rjust(50, ' '))
# 检查字符串是否全由数字构成
print(str1.isdigit()) # False
# 检查字符串是否全由字母构成
print(str1.isalpha()) # False
# 检查字符串是否由数字和字母构成
print(str1.isalnum()) # True
# 删除字符串左右两边的空格之后打印
print(str1.strip())
格式化输出字符串
a,b = 5,10
print('%d + %d = %d' % (a,b,a+b))
使用字符串提供的方式来完成字符串的格式。在python3.6以后,格式化字符串还有更为简洁的书写方法,就是在字符串前加上字母f,可使用语法简化开始的代码。
a,b = 5,10
print('{0} + {1} = {2}'.format(a,b,a+b))
print(f'{a} + {b} = {a + b}')
3.使用列表
除了字符串,Python还内置了多种类型的数据结构,如果要在程序中保存和操作数据,绝大多数可以利用现有的数据结构来实现,接下来我们就来认识一下列表。
通过前面的学习已经了解了数值类型(int和float)和字符类型(str)有区别,数值类型是有大小但是没有方向,理解为标量类型,在这类对象没有可以访问内部结构;而字符串是一种结构化的、矢量类型,我们需要根据一系列方法去解析字符串的内部结构。那列表也可以理解为矢量类型,它的值都是有序序列,每个值都可以通过索引进行标识,定义列表可以将列表的元素放在[]中,多个元素用,进行分隔,可以使用for循环对元素列表进行遍历,也可以使用[]或[:]运算符取出列表中的一个或多个元素。
list1 = [1,2,3,4,5,6,7,8,9]
print(list1)
# 乘号可以表示对列表元素的重复
list1 = list1 * 2
print(list1)
# 计算列表长度
print(len(list1))
# 索引
print(list1[0]) # 计算机有序序列的大小都是从0开始,结果为1
print(list1[-1])
# 利用索引修改列表元素
list1[4] = 100
print(list1)
# 通过循环用下标遍历列表元素
for x in range(len(list1)):
print(list1[x])
# 通过循环编列列表元素
for x in list1:
print(x)
# 通过enumerate函数处理列表之后再遍历可以同时获得元素索引和值
for key,value in enumerate(list1):
print(key,value)
对列表元素的增删改查
list1 = [1,2,3,4,5,6]
list1.append(200)
list1.insert(1,300)
print(list1)
# 合并两个列表
list1.extend([10,20,30])
list1 += [10,20,30]
print(list1)
# 移除元素
list1.remove(5)
# 根据索引删除元素
list1.pop(0)
list1.pop(len(list1) - 1)
# 清空列表
list1.clear()
当然列表也有切片的操作,通过列表我们可以实现对列表内元素的复制或者利用某些元素创建新列表。
list1 = ['a','b','c','d','e','f']
list1 += [1,2,3]
# 列表切片
list1 = list1[1:4] # b c d
# 反向切片打印
print(list1[::-1])
对列表的排序操作
list1 = ['orange', 'apple', 'zoo', 'internationalization', 'blueberry']
list2 = sorted(list1)
# sorted函数返回列表排序后的列表不会修改传入的列表
list3 = sorted(list1,reverse=True)
# reverse 排序规则参数,当等于True为降序,等于False为升序(默认)
list4 = sorted(list1,key=len)
# 通过key关键字参数指定根据字符串的长度进行排序
list.sort(reverse = True)
print(list1)
# 函数sort也是对且只能对列表进行排序,sorted是可以对所有可迭代的对象进行排序,区别在于sort会直接操作原列表,而sorted会返回一个新的列表
4.生成式和生成器
可以使用列表的生成式语法来创建列表,代码如下:
list1 = [x for x in range(1,10)]
print(list1)
list2 = [x + y for x in 'ABCDE' for y in '1234567']
print(list2)
# 用列表的生成表达式语法创建列表容器
# 用该方法创建列表后元素已经准备就绪所以需要消费较多的内存空间
list3 = [x ** 2 for x in range(1,1000)]
print(sys.getsizeof(list3)) # 查看对象占用内存的字节数
注意:接下来我们创建一个生成器并不是一个列表
# 通过生成器可以获取到数据但它不用占去额外的空间存储数据
# 每次需要数据的时候就通过内部的运算的到数据(当然这就需要额外的时间)
list4 = [x ** 2 for x in range(1,1000)]
print(sys.getsizeof(list4))
print(list4)
for value in list4:
print(value)
当然除了上面提到的生成器语法,python中还有另外一种定义生成器的方式,通过yield关键字将一个普通的函数改造成生成器函数。我们将尝试如何实现一个斐波那契额数列的生成器。
F0 = 0
F1 = 1
Fn = Fn-1 + Fn-2(n>=2)
def fib(n):
a,b = 0,1
for _ in range(n):
a,b = b,a+b
yield a
def main():
for value in fib(20):
print(value)
if __name__ == '__main__':
main()
5.元组
python中的元组和列表类似都是一种容器数据类型,可以用一个变量来存储多个数据,不同之处在于元组不能修改。从这个名字当中可以理解到把元素组合成一起就是元组的定义吧。
# 定义元组
test = ('name',jiejie,true,'四川')
print(test)
# 获取元组中的数据
print(test[0])
print(test[1])
print(test[0])
# 遍历元组中的值
for val in test:
print(val)
# 尝试给元组输入值
test[0] = "add"
# TypeError: 'tuple' object does not support item assignment
# 如果变量test引用了新的元组原来的元组将被垃圾回收
test = ('瓜娃子','就是','你')
print(test)
# 将元组转换成列表
change = list(test)
print(change)
# 列表是可以修改元素的,可以修改列表元素后将在转换成元组
new_list['a','b','c']
new_tuple = tuple(new_list)
print(new_tuple)
在这里会有一个值得思考的问题,那开发人员设计列表后为什么要设计元组这个东西呢?
1.元组是无法修改的,在实际的项目中特别是多线程来说可能使用不变的对象要好,一方面因为对象状态不能修改,所以可以避免由此引发的不必要的错误,就是不可变的对象要比可变的对象更容易维护;另一方面假设没有任何一个线程可以修改不变对象的内部状态,一个不变对象自动就是线程安全的,可以省掉处理同步化的开销。一个不变对象可以放心的被共享。如果不需要对元素进行增删改的时候可以考虑使用元组,要使一个对象返回多个值也是可以使用元组。
2.元组在创建时间和占用空间上都优于列表。python中可以使用sys模块的getsizeof函数来检查存储同样元素的列表和元组各自占用了多少的内存空间。可以使用ipython中的魔法指令%timeit来分析创建同样内容的元组和列表所花费的时间,可以在自己的电脑上做相关的测试。
6.使用集合
python中的集合跟在数学上学习到的集合是一致的,不允许有重复的元素,可以进行交集、并集、差集等运算。
a.intersection(b) # a与b求交集
a.symmetricDifference # a与b求交集以外的集
a.union(b) # a与b求并集
a.subtracting(b) # a减b
6.1 集合的创建和使用
# 创建集合的字面量语法
set1 = {1,2,3,4,5}
print(set1)
print('length=',len(set1))
# 创建集合的构造语法
set2 = set(range(1,10))
set3 = set((1,2,3,3,2,1))
print(set2,set3)
# 创建集合的推导式语法
set4 = {num for num in range(1,10) if num % 3 == 0 or num % 5 ==0}
print(set4)
6.2 向集合添加元素和删除元素
set1 = {1,2,3}
# 集合add方法:是把要传入的元素做为一个整个添加到集合中
set1.add(4)
set1.add(5)
# 集合update方法:是把要传入的元素拆分,做为个体传入到集合中
set2.update([11,12])
# discard() 方法用于移除指定的集合元素
set2.discard(5)
# 集合删除操作方法
if 4 in set2:
set.remove(4)
print(set1,set2)
# pop() 方法用于随机移除一个元素
print(set3.pop())
print(set3)
6.3 集合的成员、交集、并集、差集等运算
# 集合的交集、并集、差集、对称差运算
print(set1 & set2)
# print(set1.intersection(set2)) 交集
print(set1 | set2)
# print(set1.union(set2)) 并集
print(set1 - set2)
# print(set1.difference(set2)) 差集
print(set1 ^ set2)
# print(set1.symmetric_difference(set2)) 两个集合不重复的集
# 判断子集和超集
print(set2 <= set1)
# print(set2.issubset(set1)) 判断集合元素是否都包含在指定集合中,是则返回Ture,否则返回False
print(set3 <= set1)
# print(set3.issubset(set1))
print(set1 >= set2)
# print(set1.issuperset(set2)) 判断指定集合是否包含在原始集合中,是则返回Ture,否则返回False
print(set1 >= set3)
# print(set1.issuperset(set3))
需要注意的是:python中允许通过一些特殊的方法来为某种类型或数据结构自定义运算符,例子中对集合进行运算的时候可以调用集合对象的方法,也可以直接使用对应的运算符,例如&运算符跟函数instersection作用一样,但是用运算符可能会更加直观。
7.使用字典
字典是另一种可变容器模型,python中的字典和我们平时使用的字典概念类似,都是根据位置存储任意类型对象,方便查找。与列表和集合不同的是,字典的每个元素都是由一个键对应一个值组成的“键值对”,键和值通过冒号分开。
# 创建字典的字面量语法
dictionary = {'jietewang':90,'武则天':49,'李隆基':98}
print(dictionary)
# 创建字典的构造器语法
itmes1 = dict(ono=1,two=2,three=3,four=4)
# 通过zip函数将两个序列压缩成字典
itmes2 = dict(zip(['a','b','c'],'123'))
# 创建字典的推导式语法
itmes3 = {num:num ** 2 for num in range(1,10)}
# 通过键可以获取字典中对应的值
print(dictionary['jietewang'])
# 对字典中所有的键值进行遍历
for key in dictionary:
print(f'{key}:{dictionary[key]}')
# get方法也是通过键获取对应的值,但是可以设置默认值
print(dictionary.get('jietewang'))
print(dictionary.get('jietewang',100))
# 删除字典中的元素
print(dictionary.popitem())
# popitem() 方法返回并删除字典中的最后一对键和值。
print(dictionary.pop('jietewang',90))
# pop() 方法删除字典给定键 key 及对应的值,返回值为被删除的值。key值必须给出.否则返回default值
# 清空字典
dictionary.clear()
print(dictionary)
8.练习
练习1:在屏幕打印跑马灯文字
import os
import time
def main():
connect = '中国最强中国最棒中国最最牛......'
while True:
# 清理屏幕上的输出
os.system('cls') # system函数可以将字符串转化成命令在服务器上运行
print(connect)
# 休眠200毫秒
time.sleep(0.2)
connect = connect[1:] + connect[0]
if __name__ == '__main__':
main()
练习2:设计一个函数产生指定长度的验证码,验证码由大小写字母和数字构成
import random
def verification_code(code_len=4):
"""
生成指定长度的验证码
:param code_len 验证码的长度(默认为4)
:return 由大小写英文字母和数字构成的随机验证码
"""
all_chars = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
last_pos = len(all_chars) - 1
# print(last_pos)
code = ''
for _ in range(code_len):
index = random.randint(0,last_pos)
# print(index)
code += all_chars[index]
return code
if __name__ == '__main__':
print(verification_code())
练习3:设计一个函数返回给定文件名的后缀名
def get_suffix(filename,haveSpot=False):
"""
获取文件名的后缀名
:param filename 文件名
:param haveSpot 后缀名是否带点
:return 文件的后缀名
"""
spot = filename.rfind('.')
if 0 < spot < len(filename) - 1:
# index = spot if haveSpot else spot + 1
if haveSpot:
index = spot
else:
index = spot + 1
return filename[index:]
else:
return '你输入文件名格式不正确'
if __name__ == '__main__':
print(get_suffix('test.jsp',haveSpot=True))
练习4:设计一个函数返回传入列表中最大和第二大的元素的值
def twoNumbers(x):
max1,max2 = (x[0],x[1]) if x[0] > x[1] else (x[1],x[0])
for index in range(2,len(x)):
if x[index] > max1:
max2 = max1
max1 = x[index]
elif x[index] > max2:
max2 = x[index]
return max1,max2
练习5:计算指定的年月日是这一年的第几天
def is_leap_year(year):
"""
判断是不是闰年
: param year
: return 闰年返回Ture平年返回False
"""
return year % 4 == 0 and year % 100 != 0 or year % 400 == 0
def which_day(year,mouth,date):
"""
计算传入的日期是这一年的第几天
:param year: 年
:param month: 月
:param date: 日
:return: 第几天
"""
day_of_mouth=[
[31,28,31,30,31,30,31,31,30,31,30,31,30],
[31,29,31,30,31,30,31,31,30,31,30,31,30]
][is_leap_year(year)]
total = 0
for index in range(mouth - 1):
total += day_of_mouth[index]
return total + date
def main():
print(which_day(1980,11,28))
print(which_day(2020,8,27))
if __name__ == "__main__":
main()
练习6:打印杨辉三角
"""
原理:利用列表,根据在列表里面定义列表,将值保存到内部列表
大列表控制行,内部列表用来控制列
"""
def main():
YHTriangle = int(input('Enter the nember of row:'))
newYHTriangle = [[]] * YHTriangle
for _row in range(len(newYHTriangle)):
newYHTriangle[_row] = [None] * (_row + 1)
for _column in range(len(newYHTriangle[_row])):
if _column == 0 or _column == _row:
newYHTriangle[_row][_column] = 1 # 判断首尾为1
else:
newYHTriangle[_row][_column] = newYHTriangle[_row - 1][_column] + newYHTriangle[_row - 1][_column - 1]
print(newYHTriangle[_row][_column3],end='\t')
print(' ')
if __name__ == '__main__':
main()
练习7:双色球选号
"""
randrange:指定范围内,按指定基数递增的集合中获取一个随机数。基数=步长
randint:指定范围内获取一个整数(a<=n<=b)
sample:从指定序列中随机获取指定长度的片段,该函数不会修改原有列表
enumerate:选取数据对象(列表、元组或字符串)重新组合为组合列表,并列出数据和下标
"""
from random import randrange, randint, sample
def display(balls):
"""
输出双色球和其对应的号码
"""
for ball_key,ball_value in enumerate(balls):
if ball_key == len(balls) - 1:
print('|',end=' ')
print('%02d' % ball_value,end=' ')
print()
def random_number():
"""
随机选择一组号码
"""
red_balls = [x for x in range(1,34)]
selected_balls = []
selected_balls = sample(red_balls,6)
selected_balls.sort()
selected_balls.append(randint(1,16))
return selected_balls
def main():
saleQuan = int(input('投注注数:'))
for _ in range(saleQuan):
display(random_number())
if __name__ == '__main__':
main()
练习8:约瑟夫环
"""
约瑟夫环:话说有15个基督教徒和15个天主教徒,都被困在一个岛上,需要扔15个人到河里剩下的人才能活下来
有人机智了:那我们围城一个圈,报道9的人就被扔下河,剩下的人从1开始从新报数,以此类推。
请问要怎么样站位才能保证扔下去的15个人都是天主教徒。
"""
def main():
persons = [True] * 30
counter,index,number = 0,0,0
while counter < 15:
if persons[index]:
number += 1
if number == 9:
persons[index] = False
counter += 1
number = 0
index += 1
index %= 30
for person in persons:
print('基督教徒' if person else '天主教徒',end=' ')
if __name__ == '__main__':
main()
-------------------------------------------------------------------------
'''
变形:丢手绢,累计43个人,随机开始报数,从1开始,数到3的淘汰,又从1开始
'''
peopleNumber = 43
call = 3
num = 1
peoples = [True] * peopleNumber
result = []
while any(peoples):
for index,people in enumerate(peoples):
if people:
if num == call:
peoples[index] = False
result.append(index + 1)
num = 1
else:
num += 1
print(result)
练习9:井字棋游戏
"""
井字棋:就是画一个井字,在将空格填满。
总共有9个格,两个角色分别把方框填满并连城一条线
"""
import os
def print_board(board):
print(board['TL'] + '|' + board['TM'] + '|' + board['TR'])
print('-+-+-')
print(board['ML'] + '|' + board['MM'] + '|' + board['MR'])
print('-+-+-')
print(board['BL'] + '|' + board['BM'] + '|' + board['BR'])
def main():
init_board = {
'TL': ' ', 'TM': ' ', 'TR': ' ',
'ML': ' ', 'MM': ' ', 'MR': ' ',
'BL': ' ', 'BM': ' ', 'BR': ' '
}
begin = True
while begin:
curr_board = init_board.copy()
begin = False
turn = 'x'
counter = 0
os.system('cls')
print_board(curr_board)
while counter < 9:
move = input('%s请下棋,请输入位置:' % turn)
if curr_board[move] == ' ':
counter += 1
curr_board[move] = turn
if turn == 'x':
turn = 'o'
else:
turn = 'x'
os.system('cls')
print_board(curr_board)
choice = input('再玩一局?(yes|no)')
begin = choice == 'yes'
if __name__ == '__main__':
main()