目录
摘要
一、课程设计目的及内容
创新功能:
二、算法及设计过程分析
1.总流程
2.主界面
3.文件处理与生成单链表
4.查看所有联系人信息
5.查看人数
6.查找联系人(以姓名或号码为依据)
7.对姓名或号码输入关键字进行模糊查找
8.添加联系人
9.删除联系人
10.合并两个通讯录
三、实验结果分析与总结
1.实验结果
2.总结
四、完整代码
1.单链表模块(LinkList.py)
2.主代码
摘要
本课程设计结合本学期所学的数据结构和以前学习的python程序语言和c语言知识,数组、函数、结构体、指针、链表、文件读取操作等等,设计开发一个简单的通讯录管理程序。设计开发这个系统需要用到链表、文件读取操作、结构体、函数、指针、等C语言知识。本课程设计将会实现的功能包括:读取计算机文件里的通讯录联系人信息(姓名、号码、分组),并生成以名字为排序依据的单链表、统计联系人个数、对联系人信息进行准确查找和模糊查找、新增和删除联系人以及将两个通讯录合并。本着简单、易用的设计原则,设计出一款通讯录管理的程序。
一、课程设计目的及内容
目的:用 python设计一个具有单向链表结构的通讯录管理程序,用于查询和修改
等便捷化操作通讯录信息文件。
内容:
定义一个包含手机联系人信息(姓名,电话,分组)的链表,使其具有如下功能:
(1) 从计算机已保存的文件读入联系人信息,并用直接插入排序法从无到有建立以姓名为排序 依据的单链表;
(2) 统计联系人个数;
(3) 根据联系人姓名或电话进行查找,成功返回联系人记录;
(4) 实现通过姓名的部分字眼进行查询时,显示包含该字眼的所有联系人记录;
(5) 对于一个从键盘数据的新增联系人,能插入到已排好序的链表中;
(6) 删除指定姓名或电话的联系人记录。
创新功能:
实现两个手机通讯录链表的合并,合并之后仍然以名字为关键字排序。相同姓名、相同电话号码的记录删除其中一个,相同姓名、不同电话号码的记录通过输出询问进行电话号码合并或分别保存。
文章来源地址https://www.toymoban.com/news/detail-793866.html
二、算法及设计过程分析
1.总流程
文章来源:https://www.toymoban.com/news/detail-793866.html
2.主界面
每个功能对应一个模块,模块引用读取文件后生成的单链表信息,其中修改后的链表也会写入文件
3.文件处理与生成单链表
(1)使用.csv文件的优点:每一行都是一个数据元素作为节点,而每个数据项都存放在一个单元格里(如右图所示),从而对其进行读取就非常简单了。在python中,对csv文件的读取会每行以列表的默认形式输出,而列表只需要输入下标即可输出每行每列的信息,从而输入到链表时每个结点里面的数据也是一个保存着联系人信息的列表,然后对链表的结点处理也能按照列表方式进行,非常方便。
(2)由于在python中,中文字符串的比较经常会出错,比如:
>>> "啊">"吧"
True
很明显,”啊”在中文里的拼音应该小于”吧”,而且如果比较的两个字符串里的中文长度不一样比较结果也千变万化。所以需要进行对中文转拼音后再进行比较,可以完全解决这个问题。所以编写了以下代码进行转换拼音比较(需要第三方库):
#中文字符串比较问题
from pypinyin import pinyin,Style
from itertools import chain
def toPinyin(s):
return ' '.join(chain.from_iterable(pinyin(s, style=Style.TONE3)))
结果输出也非常有效,也可以看出,中文被转换成了拼音形式,能正确进行中文比较了。
>>> print(toPinyin("啊")>toPinyin("吧"))
False
>>> print(toPinyin("数据结构"))
shu4 ju4 jie2 gou4
4.查看所有联系人信息
这个很简单,直接打印链表即可。
但是存在两个问题:
一是链表直接打印只会返回一串形如 <address_book.Linklist.LinkedList object at 0x0000028984E5F130> 的十六进制的地址,所以打印需要将链表遍历一遍所有结点,因此重写链表的repr或str函数,该函数就是在类里面遍历一遍所有内容,然后返回一个对象的string,就可以直接打印链表了。
二是由于链表的数据元素以列表形式存放,肯定每个数据项的长度不一,因此输出后对齐就非常重要了。而python里面可以用ljust()或rjust()进行左或右对齐。
5.查看人数
直接调用链表函数ListLength()就可以了查看链表长度(即总人数)了。
而查看分组人数就需要先筛选出所有分组,然后再统计每个分组的人数,用两个循环就完成了。
6.查找联系人(以姓名或号码为依据)
7.对姓名或号码输入关键字进行模糊查找
8.添加联系人
9.删除联系人
输入信息后,遍历链表。若在链表中查找到该数据项则删除,否则提示无该联系人。
修改源通讯录文件:遍历文件的每一行的相关数据项(名字和号码),搜索到即删除该行信息。
10.合并两个通讯录
输入两个文件路径后生成两个链表,若出现相同的联系人信息则合并,相同的名字不同的电话号码则分别输出两个信息后询问是合并号码还是分别保存
三、实验结果分析与总结
1.实验结果
程序设计的运行结果比较理想,但是也有许多不足。比如排版尚未完全对齐,原版本在合并链表时发现相同的信息没有显示出来,左图。所以答辩结束后及时进行了修改,右图。
2.总结
通过完成这个通讯录管理的程序,很大程度上进一步了解了有关链表和结构体课程理论知识,这也是单纯的课堂上学不到的。“实践即是检验真理的唯一标准”,只有通过自己动手,才能更加了解理论知识,也才能更好地去一步步熟悉运用。
做程序不仅仅是敲代码这么简单。当然,扎实的编程基础是最基本的,但是更需要的是注意力的集中和清晰地逻辑思维能力,这样在设计算法的时候才不会那么容易被自己写的东西绕晕,最好是在本子上写出或脑海里形成一个脉络框架然后一步步完成。最后再细心调试,修改bug,优化程序编译界面,添加必要注释,这一点也非常重要,因为有时候数据太多导致面板太乱,自己也会忘了数据内容(在做排序比较时改bug改到凌晨三点,最后发现居然是少打了个半括号...)。
其次,团队合作能力也非常重要,总有擅长和不擅长的,所以在队伍里主动承担自己擅长的部分,遇到自己不熟悉的小组内一起解决,这样能大大提高工作效率。
四、完整代码
文件夹下:__init__.py为空文件,方便导入同目录下的模块包, .csv文件需要自己新建并以右图格式存放信息,首行为标注信息,第一列姓名,第二列号码,第三列分组
1.单链表模块(LinkList.py)
#此模块为单链表以及各个函数的定义模块
class LNode:
"""链表结点,名称为LNode"""
def __init__(self, elem: int or str, next=None):
self.elem = elem #结点数据域
self.next = next #结点指针域
class LinkedList:
"""链表"""
def __init__(self):
#链表初始化
self.head = None #头结点指针域为空
def ListEmpty(self):
"""判断是否为空链表,头节点为None则是空"""
return self.head is None
def ListLength(self):
"""求链表的长度"""
p = self.head
count = 0
while p:
count += 1
p = p.next
return count
def ListClear(self):
"""清空链表"""
self._head = None
def LocateElem(self, value: int or str):
# 根据值查找节点,函数返回结点
p = self.head
while p and p.elem != value:
p = p.next
if p==None:
return None
else:
return p
def LocateElem_first(self, value: int or str):
# 根据值查找节点第一个元素项,函数返回是否查找到
p = self.head
while p and p.elem[0] != value:
p = p.next
if p==None:
return False
else:
return True
def GetElem(self, position: int or str):
# 根据索引位置查找节点,需要挨个遍历,返回数据域的值
p = self.head
index = 0
while p and index != position:
p = p.next
index += 1
if p==None:
return None
else:
print(p.elem)
return p.elem
##########################################################
#前插法
def insert_node_to_head(self, node): #创建新节点插入表头,因之后函数需要用到插入新节点,所以单独创建函数
if node:
node.next = self.head
self.head = node
def insert_value_to_head(self, value: int or str): #将数据赋予表头结点数据域
node = LNode(value)
self.insert_node_to_head(node)
##########################################################
def insert_elem_to_last(self, elem):
#尾插法
node = LNode(elem)
p = self.head
if not p:
self.head = node
else:
while p.next:
p = p.next
p.next = node
##########################################################
def insert_node_after(self, node, new_node):
#插在指定元素后
if not node or not new_node:
return
new_node.next = node.next
node.next = new_node
def insert_value_after(self, old_node, new_value):
new_node = LNode(new_value)
self.insert_node_after(old_node, new_node)
#########################################################
def insert_node_before(self, node, new_node):
#插在指定元素前
if not self.head or not node or not new_node:
return
if node == self.head:
self.insert_node_to_head(new_node)
return
p = self.head
while p and p.next != node:
p = p.next
if not p:
return
new_node.next = node
p.next = new_node
def insert_value_before(self, value: int or str, node):
new_node = LNode(value)
self.insert_node_before(node, new_node)
#########################################################
def delete_by_node(self, node):
# 删除某个节点
if not self.head or not node:
return
# 不是最后一个节点,可以直接删除, 删除的复杂度为O(1)
if node.next:
node.elem = node.next.elem
node.next = node.next.next
else:
p = self.head
while p and p.next != node:
p = p.next
if not p:
return
p.next = node.next
def delete_by_value(self, value: int or str):
# 删除某个值对应的节点
if not self.head or not value:
return
fake_head = LNode(-1)
fake_head.next = self.head
prev, current = fake_head, self.head
while current:
if current.elem != value:
prev.next = current
prev = prev.next
current = current.next
if prev.next: # current.item == value
prev.next = prev.next.next
self.head = fake_head.next
######################################################
def InsertSort(self):
if not self.head:
return
fakehead=LNode(None) #创建哨兵
p=self.head
while p:
t=p.next #下一结点
q=fakehead #将每一次遍历的结点赋予哨兵
#找到p结点插入的位置
while q.next and q.next.elem<=p.elem:
q=q.next
p.next=q.next
q.next=p
p=t
######################################################
def __repr__(self): #由于直接将链表结果打印出来会变成一串十六进制码,所以需要进行重写函数
# 打印链表,重写repr或者str函数
a = []
p = self.head
while p:
a.append(p.elem)
p = p.next
return "\t\n".join(map(str, a))
2.主代码
import os
import csv
from pypinyin import pinyin,Style
from itertools import chain
import re
from Linklist import * #同目录下找到LinkList.py文件,例如LinkList.py 在文件夹address_book下,
#则修改为from address_book.LinkList import *(需要在目录下新建一个__init__.py的空文件)
#或者将LinkList.py文件复制到环境包目录下,或者将所有代码放在同一个代码文件里不使用import
import pandas
#中文字符串比较
def toPinyin(s):
return ' '.join(chain.from_iterable(pinyin(s, style=Style.TONE3)))
#判断字符串是否为中文
def is_CN (mail_text: str):
tmp_text = ''.join(mail_text.split())
count = 0
for s in tmp_text.encode('utf-8').decode('utf-8'):
if u'\u4e00' <= s <= u'\u9fff':
count += 1
if float(count / (tmp_text.__len__())) > 0.1:
return True
else:
return False
class LinkList:
#链表初始化读入文件信息,并排序
def __init__(self):
self.l=LinkedList()
global path #文件路径
while 1:
# path = input(r"请输入通讯录文件路径,例如C:\book.csv:""\n")
path=r"info.csv" #csv文件空行不能大于1,否则新增联系人会报错或每次多一个空行
if not os.path.exists(path):
print("->文件不存在,请重新选择文件\n")
f=open(path,'r',encoding='utf-8')
read=csv.reader(f)
for i,lines in enumerate(read):
if i>=1 and lines:
empty=0
break
if not os.path.getsize(path):
f.close()
print("->文件为空,已为您增加表头注释信息,稍后请增加新联系人")
first = ['姓名(names)', '电话号码(tel_number)', '分组(group)']
f=open(path,'a',newline='')
writer=csv.writer(f)
writer.writerow(first)
f.close()
empty=1
continue
else:
if next(read):
empty = 0
break
if not next(read):
print("联系人为空,请重新选择文件或手动添加联系人")
empty=1
break
if empty==0:
f=open(path,'r',encoding='utf-8') #以读取方式打开文件
reader=csv.reader(f)
next(reader) #第一行为注释信息,从第二行开始读取
sort_info=[] #获取排列信息
for lines in reader:
sort_info.append(lines[0])
f.close()
sort_info=sorted(sort_info,key=toPinyin) #若有中文通过拼音进行排序
for i in range(len(sort_info)): #将按照姓名排序好的信息依次插入单链表
f = open(path, 'r',encoding='utf-8')
reader = csv.reader(f)
next(reader)
for lines in reader:
if sort_info[i]==lines[0]:
self.l.insert_elem_to_last(lines)
f.close()
# 功能1,显示以名字为排序的单链表表示的通讯录
def fun1(self):
if self.l.ListLength()>0:
print("-"*87)
print("->信息按姓名进行从小到大排序,姓名信息的优先序列分别为:数字、大写字母开头英文字母、中文(小写英文开头)")
print("-" * 87)
#打印信息对齐
s=["姓名(name)","电话(tel_number)","分组(group)"]
maxname=len(s[0])
maxnum=len(s[1])
maxgroup=len(s[2])
p=self.l.head
while p:
if len(p.elem[0])>maxname:
maxname=len(p.elem[0])
if len(p.elem[1])>maxnum:
maxnum=len(p.elem[1])
if len(p.elem[2])>maxgroup:
maxgroup=len(p.elem[2])
p=p.next
maxlen=max(maxnum, maxgroup, maxname)
print(s[0].ljust(maxlen, " "), s[1].ljust(maxlen, " "), s[2].ljust(maxlen, " "))
p=self.l.head
while p:
if is_CN(p.elem[0]): #中文对齐英文比较困难,放弃了,就这样吧
print(p.elem[0].ljust(maxlen-1, " "),
p.elem[1].ljust(maxlen, " "), p.elem[2].ljust(maxlen, " "))
else:
print(p.elem[0].ljust(maxlen, " "),
p.elem[1].ljust(maxlen, " "), p.elem[2].ljust(maxlen, " "))
p=p.next
else:
print("->通讯录为空")
#功能2,获取通讯录人数
def fun2(self):
print("->通讯录总人数:", self.l.ListLength())
p=self.l.head
if p!=None: #输出每个分组人数
group=[p.elem[2]]
while p:
if not p.elem[2] in group:
group.append(p.elem[2])
p=p.next
num=[]
for i in range(len(group)):
sum=0
p = self.l.head
while p:
if p.elem[2]==group[i]:
sum+=1
p=p.next
num.append(sum)
max=len(group[0])
for i in range(len(group)):
if len(group[i])>max:
max=len(group[i])
print("每组人数分别是:")
for i in range(len(group)):
print(group[i].ljust(max," "),num[i])
else:
print("暂无分组信息")
#功能3,根据姓名或号码查找联系人信息
def fun3(self):
while 1:
print("\n")
p=self.l.head
if p==None:
print("->通讯录为空,请新增联系人后再查找")
break
info=input("->输入想要查找的联系人姓名或电话号码,输入exit可退出: ")
if info=='exit':
os.system("cls")
break
flag=0
while p:
if p.elem[0]==info or p.elem[1]==info:
print(p.elem,"\n")
flag=1
break
else:
p=p.next
if p==None and flag==0:
print("\n->查找失败,请重新输入,或输入exit退出。\n")
break
#功能4模糊查找
def fun4(self):
while 1:
if self.l.head==None:
print("通讯录为空,请新增联系人")
break
print("->请选择:\n"
"1.模糊查找姓名\n"
"2.模糊查找电话号码\n")
choice=input("->请输入功能选择序号,输入exit退出模糊查找: " )
if choice=='1':
info=input("->请输入要查找的信息: ")
p=self.l.head
flag=0
while p:
if re.findall(info,p.elem[0]):
print(p.elem)
print()
flag=1
p=p.next
if p==None and flag==0:
print("-=>无您所查找的信息")
print("-"*50)
elif choice=='2':
info=input("->请输入要查找的信息: ")
p=self.l.head
flag=0
while p:
if re.findall(info,p.elem[1]):
print(p.elem)
print()
flag=1
p=p.next
if p==None and flag==0:
print("->无您所查找的信息")
print("-"*50)
elif choice=='exit':
break
else:
print("->输入有误")
print("-" * 50)
#功能5新增联系人
def fun5(self):
p=self.l.head
if p==None: #通讯录为空时不显示分组信息
print("当前无联系人信息,请新增联系人")
newname=input("->请输入新联系人名字: ")
newnumber=input("->请输入新联系人号码: ")
newgroup = input("->请输入新联系人加入的分组: ")
print("\n")
newinfo = [newname, newnumber, newgroup]
f = open(path, 'a',encoding="utf-8",newline='')
writer = csv.writer(f)
writer.writerow(newinfo) # 将新信息写入文件
f.close()
p.elem=newinfo
print("->已经增加新联系人:\n姓名:{},电话号码:{},分组:{}".format(newname, newnumber, newgroup))
print(self.l)
else: #通讯录不为空时显示分组信息
newname=input("->请输入新联系人名字: ")
newnumber=input("->请输入新联系人号码: ")
group=[self.l.head.elem[2]]
p=self.l.head
while p:
if not p.elem[2] in group:
group.append(p.elem[2])
p=p.next
print(group)
newgroup=input("->请输入新联系人加入的分组,目前所有分组如上(若无该分组则自动创建分组): ")
newinfo=[newname,newnumber,newgroup]
f=open(path,'a',encoding='utf-8',newline='')
writer=csv.writer(f)
writer.writerow(newinfo) #将新信息写入文件
f.close()
p = self.l.head
while p:
if p.next==None:
self.l.insert_elem_to_last(newinfo)
break
elif toPinyin(p.elem[0]) < toPinyin(newinfo[0]) and\
toPinyin(p.next.elem[0]) > toPinyin(newinfo[0]):
self.l.insert_value_after(p,newinfo)
break
elif toPinyin(p.elem[0])>toPinyin(newinfo[0]):
self.l.insert_value_to_head(newinfo)
break
else:
p=p.next
print("->已经增加新联系人:\n姓名:{},电话号码:{},分组:{}".format(newname,newnumber,newgroup))
#6,通过姓名或号码删除联系人
def fun6(self):
delete=input("->请输入想要删除的联系人姓名或号码: ")
p=self.l.head
while p:
if p.elem[0]==delete or p.elem[1]==delete:
self.l.delete_by_node(p)
print("->已删除")
break
if p.next==None:
print("->联系人信息不存在!")
break
p=p.next
#查找删除信息在文件中的位置
f=open(path,'r',encoding='utf-8')
i=1
reader=csv.reader(f)
next(reader)
for lines in reader:
if lines[0]==delete or lines[1]==delete:
break
i+=1
f.close()
#在文件中删除该信息行
data = pandas.read_csv(path,encoding='utf-8')
data.drop(data.index[i-1], inplace=True)
data.to_csv(path, index=0)
#合并两个通讯录,先合并,后排序
def connnect():
while 1:
path1=input(r"->请输入通讯录1文件路径: ")
path2=input(r"->请输入通讯录2文件路径: ")
if os.path.exists(path1) and os.path.exists(path2):
break
else:
print("->其中有文件不存在,请重新输入:")
#通讯录1
l1=LinkedList()
f1=open(path1,'r',encoding='utf-8')
reader1=csv.reader(f1)
next(reader1)
for lines in reader1: #将文件信息写入链表,暂不排序
l1.insert_elem_to_last(lines)
#通讯录2
l2=LinkedList()
f2=open(path2,'r',encoding='utf-8')
reader2=csv.reader(f2)
next(reader2)
for lines in reader2:
l2.insert_elem_to_last(lines)
#两个链表合并后将新链表的内容写入新文件保存
l3=LinkedList()
p1=l1.head
while p1:
p2=l2.head
while p2:
if p1.elem[0]==p2.elem[0] and p1.elem[1]==p2.elem[1]: #两个通讯录相同的
l3.insert_elem_to_last(p1.elem)
p2=p2.next
continue
if p1.elem[0]==p2.elem[0] and p1.elem[1]!=p2.elem[1]:
while 1:
print("检查到有联系人号码不相同:\n",p1.elem,"\n",p2.elem)
print("请选择:\n"
"1.合并号码\n"
"2.分别保存\n")
choi=input("输入选择: ")
if choi=='1':
l3.insert_elem_to_last(p1.elem)
p3=l3.LocateElem(p1.elem)
p3.elem[1]=p1.elem[1]+'、'+p2.elem[1]
p2=p2.next
break
if choi=='2':
l3.insert_elem_to_last(p1.elem)
l3.insert_elem_to_last(p2.elem)
p2=p2.next
break
else:
print("输入有误,请重新输入")
continue
else:
p2=p2.next
p1=p1.next
print("重复的信息有\n:",l3)
print()
#将剩余没有重复的加入l3
p1=l1.head
p2=l2.head
p3=l3.head
if p3!=None:
while p1:
if not l3.LocateElem_first(p1.elem[0]):
l3.insert_elem_to_last(p1.elem)
p1=p1.next
while p2:
if not l3.LocateElem_first(p2.elem[0]):
l3.insert_elem_to_last(p2.elem)
p2=p2.next
if p3==None:
l3=l1
while p2:
p3=l3.head
while p3:
if p2.elem[0]!=p3.elem[0]:
l3.insert_elem_to_last(p2.elem)
p3=p3.next
p2=p2.next
l1.ListClear()
l2.ListClear()
#l3排序后写入文件
sortl=[]
p=l3.head
while p:
sortl.append(p.elem)
p=p.next
sortl=sorted(sortl,key=toPinyin)
p=l3.head
i=0
while p:
p.elem=sortl[i]
p=p.next
i+=1
print("合并后的通讯录为\n",l3)
print()
#写入文件
first = ['姓名(names)', '电话号码(tel_number)', '分组(group)']
f = open('connect.csv', 'a', encoding='utf-8',newline='')
writer = csv.writer(f)
writer.writerow(first)
f.close()
p=l3.head
while p:
f=open('connect.csv','a',encoding='utf-8',newline='')
writer=csv.writer(f)
writer.writerow(p.elem)
p=p.next
f.close()
#主界面
def choose():
os.system("cls")
devide = "--"
print(devide * 40)
print(" 1.查看通讯录\n"
" 2.查看通讯录人数\n"
" 3.查找联系人\n"
" 4.模糊查找通讯录(输入姓名)\n"
" 5.新增联系人\n"
" 6.删除联系人\n"
" 7.合并两个通讯录\n"
" 8.退出\n"
"-------------------------------------------------------------------------------------"
"-----------------------------作者:华南农业大学 2021人工智能系 VIK------------------------"
"-----------------------------如有使用请标明参考地址出处--------------------------------------")
print(devide * 40, )
#主函数
if __name__=='__main__':
l=LinkList()
devide="--"
choose()
while 1:
choiec=input("->请输入功能对应的序号,输入menu唤出功能菜单,按回车键结束: ")
os.system("cls")
if choiec== '1':
os.system("cls")
try:
l.fun1()
input("点击回车返回主界面")
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec== '2':
try:
l.fun2()
input("点击回车返回主界面")
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec== '3':
try:
l.fun3()
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec== '4':
try:
l.fun4()
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec== '5':
try:
l.fun5()
input("点击回车返回主界面")
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec== '6':
try:
l.fun6()
input("点击回车返回主界面")
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec=='7':
try:
connnect()
input("点击回车返回主界面")
choose()
except:
print("未知错误,请重启选择菜单")
continue
elif choiec=='8':
break
elif choiec=='menu':
os.system("cls")
choose()
continue
else:
print("***************************输入有误,请重新输入*****************************")
print(devide*40)
到了这里,关于python数据结构实验设计(单链表):通讯录管理的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!