주어진 사전 파일의 단어들을 연결리스트로 담아 사전 프로그램을 만든다.
⚡️ 단어 사전 파일
⚡️ 단어 사전 데이터로 연결리스트 만들기
완성된 프로그램의 실행 예시는 다음과 같다.
이중 연결리스트로 만들었다.
⚡️ Why?
🛠 Doubly Linked List class
class dbList:
def __init__(self):
self.head = None # 링크드 리스트의 가장 앞 노드
self.tail = None # 링크드 리스트의 가장 뒤 노드
self.mid = None # 링크드 리스트의 중간 위치 노드
self.pos = 0 # mid 의 위치를 조정하기 위한 flag
self.size = 0 # 링크드 리스트의 노드의 개수
head(맨 앞), tail(맨 뒤) 변수 외에도 mid(중간) 변수를 선언한 것을 확인할 수 있다.
또한 mid의 위치를 연결 리스트의 중앙으로 조정해주기 위한 변수 pos와
연결 리스트의 전체 node의 개수를 나타내는 size변수도 선언하였다.
🛠 class Node
class Node:
def __init__(self, data):
self.data = data # 실제 노드가 저장하는 데이터
self.next = None # 다음 노드에 대한 레퍼런스
self.prev = None # 전 노드에 대한 레퍼런스
노드가 저장하는 데이터(단어와 그 뜻) data,
현재 노드의 다음 노드에 대한 레퍼런스를 나타내는 변수 next,
그리고 다음 노드에 대한 레퍼런스를 나타내는 변수 prev를 선언하였다.
🛠 isEmpty( )
def isEmpty(self):
if self.size != 0:
return False
else:
return True
연결 리스트가 비어 있는지 판단해주는 메소드이다.
🛠 insert( )
def insert(self, pre, data):
new_node = Node(data)
if pre is self.tail: # 가장 마지막 순서 삽입
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_node
else: # 두 노드 사이에 삽입
new_node.prev = pre
new_node.next = pre.next
pre.next.prev = new_node
pre.next = new_node
self.size += 1
연결 리스트의 원하는 위치에 노드를 삽입해주는 메소드이다.
삽입할 노드의 데이터(data)와 삽입할 위치 앞의 노드(pre)의 위치를 매개변수로 받는다.
🛠 append( )
def append(self, data):
new_node = Node(data)
if self.isEmpty():
self.head = new_node
self.tail = new_node
self.mid = new_node
else:
temp = self.head
self.head = new_node
temp.prev = self.head
new_node.next = temp
self.size += 1
비어 있는 연결 리스트, 혹은 비어 있지 않은 연결리스트의 맨 앞에 노드를 삽입하는 메소드이다.
🛠 makeDict( )
def makeDict(self, data):
randdict의 단어들을 순서대로 가져와 정렬한 채로 링크드 리스트에 넣어 주는 메소드이다.
이 메소드의 작동 방식은 다음과 같다.
iterator = self.mid
우선 연결 리스트를 돌아다니며 새로운 단어가 추가 될 위치를 탐색하게 될 변수 iterator를 선언
해주고, 해당 변수의 위치를 연결리스트의 중앙(mid)으로 초기화 시켜준다.
앞서 설명했던 대로 매번 연결 리스트의 맨 앞부터 탐색하는 것보단
중앙에서 탐색을 시작하는 것이 보다 효율적일 것 같다고 생각하여 위와 같은 과정을 수행하였다.
그리고는 케이스를 분류하여 각 케이스마다 알맞은 작업을 수행하였다.
⚡️ Case
1) 리스트가 비어 있을 경우
append 메소드로 새로운 단어를 추가해준다.
2) 리스트가 비어 있지 않을 경우
2-1) 추가될 단어가 iterator 위치에 해당하는 단어 보다 작을 때 (알파벳 순서상 앞에 위치)
a. iterator 앞에 노드가 없을 경우
b. 추가될 단어가 iterator.prev에 해당하는 단어 보다 작을 경우
c. 추가될 단어가 iterator.prev에 해당하는 단어 보다 클 경우 (알파벳 순서상 뒤에 위치)
d. 추가될 단어가 iterator.prev에 해당하는 단어와 같을 경우
(randdict에는 중복된 단어가 존재하지 않는데 위와 같은 경우가 발생하는 이유는 lower( )함수를
사용하여 모든 단어를 소문자로 변환한 뒤 해당 단어들을 비교하기 때문이다.)
d-1) lower를 떼고 비교 했을 때, 추가될 단어가 iterator.prev에 해당하는 보다 작을 경우
d-2) lower를 떼고 비교 했을 때, 추가될 단어가 iterator.prev에 해당하는 보다 클 경우
2-2) 추가될 단어가 iterator 위치에 해당하는 단어 보다 클 때
a. iterator 뒤에 노드가 없을 경우
b. 추가될 단어가 iterator.next에 해당하는 단어 보다 클 경우
c. 추가될 단어가 iterator.next에 해당하는 단어 보다 작을 경우
d. 새로 추가될 단어가 iterator.next에 해당하는 단어와 같을 경우
d-1) lower를 떼고 비교 했을 때, 추가될 단어가 iterator.next에 해당하는 단어보다 작을 경우
d-2) lower를 떼고 비교 했을 때, 추가될 단어가 iterator.next에 해당하는 단어보다 클 경우
3) pos값의 변화
앞서 설명한대로 pos는 mid의 위치를 연결 리스트의 중앙으로 조정해주기 위한 변수이다.
size가 짝수일때는 이 pos의 값이 0, -1 혹은 +1이면 iterator의 위치가 mid(중앙)이고,
홀수일때는 pos의 값이 0이면 iterator의 위치가 mid(중앙)이다.
해당 작업이 잘 수행되도록 하기 위해 iterator, 즉 mid가 연결리스트의 왼쪽으로 이동할 경우엔
pos에 -1을, 오른쪽으로 이동할 경우엔 pos에 +1을 해준다.
이는 곧 mid를 기준으로 왼쪽과 오른쪽의 균형을 맞춰주는 것을 의미한다.
그러다 균형이 깨지면 (pos = -2 or pos = +2) 다음과 같은 작업을 수행한다.
위와 같은 과정들을 통해 mid의 위치를 계속하여 연결리스트의 중앙으로 유지시킬 수 있다.
🛠 search( )
def search(self, target):
연결 리스트에서 원하는 단어를 탐색하기 위한 메소드이다.
해당 메소드도 마찬가지로 연결리스트의 중간부터 탐색을 시작한다.
그리고는 케이스를 분류하여 각 케이스마다 알맞은 작업을 수행하였다.
⚡️ Case
1) 찾고자 하는 단어가 iterator에 해당하는 단어와 같을 경우 (lower( )를 수행한 값)
a. lower를 떼고 비교 했을 때, 찾고자 하는 단어가 iterator에 해당하는 단어와 같을 경우
b. lower를 떼고 비교 했을 때, 찾고자 하는 단어가 iterator 보다 작을 경우
b-1) 이동한 위치에 해당하는 단어와 찾고자 하는 단어가 일치할 경우
b-2) 이동한 위치에 찾고자 하는 단어가 없을 경우
c. lower를 떼고 비교 했을 때, 찾고자 하는 단어가 iterator 보다 클 경우
c-1) 이동한 위치에 해당하는 단어와 찾고자 하는 단어가 일치할 경우
c-2) 이동한 위치에 찾고자 하는 단어가 없을 경우
2) 찾고자 하는 단어가 iterator에 해당하는 단어 보다 작을 경우
a. iterator 앞에 노드가 없을 경우
찾고자 하는 단어가 연결 리스트 상에 존재하지 않는 경우이기 때문이다.
b. 찾고자 하는 단어가 iterator.prev에 해당하는 단어 보다 클 경우
이 또한 찾고자 하는 단어가 연결 리스트 상에 존재하지 않는 경우이다.
왜냐하면 이는 target을 찾아 특정 방향으로 탐색을 계속해서 진행하다가(one way)
한번 그 방향이 반대 방향으로 틀어졌음, 즉 target이 연결 리스트 상에 존재하지 않음을 의미하기 때문이다.
c. 위의 경우들에 해당하지 않는다면
3) 찾고자 하는 단어가 iterator에 해당하는 단어 보다 클 경우
a. iterator 뒤에 노드가 없을 경우
찾고자 하는 단어가 연결 리스트 상에 존재하지 않는 경우이기 때문이다.
b. 찾고자 하는 단어가 iterator.next에 해당하는 단어 보다 작을 경우
이 또한 찾고자 하는 단어가 연결 리스트 상에 존재하지 않는 경우이다.
왜냐하면 이는 target을 찾아 특정 방향으로 탐색을 계속해서 진행하다가(one way)
한번 그 방향이 반대 방향으로 틀어졌음, 즉 target이 연결 리스트 상에 존재하지 않음을 의미하기 때문이다.
c. 위의 경우들에 해당하지 않는다면
🛠 newWord( )
def newWord(self, inputWord):
찾고자 하는 단어가 없을 경우, 해당 단어를 연결 리스트에 추가해주는 메소드이다.
"""Node 클래스"""
class Node:
def __init__(self, data):
self.data = data # 실제 노드가 저장하는 데이터
self.next = None # 다음 노드에 대한 레퍼런스
self.prev = None # 전 노드에 대한 레퍼런스
"""더블 링크드 리스트 클래스"""
class dbList:
def __init__(self):
self.head = None # 링크드 리스트의 가장 앞 노드
self.tail = None # 링크드 리스트의 가장 뒤 노드
self.mid = None # 링크드 리스트의 중간 위치 노드
self.pos = 0 # mid의 위치를 조정하기 위한 flag
self.size = 0 # 링크드 리스트의 노드의 개수
"""링크드 리스트가 비어 있는지 판단하는 메소드"""
def isEmpty(self):
if self.size != 0:
return False
else:
return True
"""링크드 리스트의 원하는 위치에 노드를 삽입하는 메소드"""
# 삽입할 노드의 데이터와 삽입할 위치 앞의 노드(pre)의 위치를 매개변수로 받는다
def insert(self, pre, data):
new_node = Node(data)
if pre is self.tail: # 가장 마지막 순서 삽입
self.tail.next = new_node
new_node.prev = self.tail
self.tail = new_node
else: # 두 노드 사이에 삽입
new_node.prev = pre
new_node.next = pre.next
pre.next.prev = new_node
pre.next = new_node
self.size += 1
"""비어 있는 링크드 리스트, 혹은 비어 있지 않은 리스트의 맨 앞에 노드를 삽입하는 메소드"""
def append(self, data):
new_node = Node(data)
if self.isEmpty(): # 링크드 리스트가 비어 있을때
self.head = new_node
self.tail = new_node
self.mid = new_node
else: # 비어 있지 않을때 : 리스트의 맨 앞에 노드 삽입
temp = self.head
self.head = new_node
temp.prev = self.head
new_node.next = temp
self.size += 1
"""randdict의 단어들을 순서대로 가져와 정렬한 채로 링크드 리스트에 넣어 주는 메소드"""
def makeDict(self, data):
if self.isEmpty(): # 리스트가 비어 있을 경우
self.append(data)
else: # 리스트가 비어 있지 않을 경우
iterator = self.mid # 리스트의 중간부터 새로 추가될 단어가 삽입될 위치 탐색
if data[0].lower() < iterator.data[0].lower(): # data[0]이 mid.data[0] 보다 작을 때
while True:
if iterator.prev is None: # iterator 앞에 노드가 없을 경우
self.append(data) # 리스트의 맨 앞에 바로 추가
break
elif data[0].lower() < iterator.prev.data[0].lower(): # 새로 추가될 단어가 iterator.prev 보다 작을 경우 (알파벳 순서상 앞에 위치)
iterator = iterator.prev # iterator 위치 한칸 앞으로 이동
elif data[0].lower() > iterator.prev.data[0].lower(): # 새로 추가될 단어가 iterator.prev 보다 클 경우 (알파벳 순서상 뒤에 위치)
self.insert(iterator.prev, data) # iterator 와 iterator.prev 사이에 해당 단어 삽입
break
elif data[0].lower() == iterator.prev.data[0].lower(): # 새로 추가될 단어가 iterator.prev 와 같을 경우 (lower() 때문에)
if data[0] < iterator.prev.data[0]: # 원본으로 복구 시켜 비교했을때, 새로 추가될 단어가 iterator.prev 보다 작을 경우(알파벳 순서상 앞에 위치)
if iterator.prev.prev is None: # iterator.prev 앞에 노드가 없을 경우
self.append(data) # 리스트의 맨 앞에 바로 추가
break
else:
self.insert(iterator.prev.prev, data) # 아닐 경우엔 iterator.prev 와 iterator.prev.prev 사이에 해당 단어 삽입
break
elif data[0] > iterator.prev.data[0]: # 원본으로 복구 시켜 비교했을때, 새로 추가될 단어가 iterator.prev 보다 클 경우(알파벳 순서상 뒤에 위치)
self.insert(iterator.prev, data) # iterator 와 iterator.prev 사이에 해당 단어 삽입
break
self.pos -= 1 # 링크드 리스트의 왼쪽으로 이동할 경우엔 pos에 -1
else: # data[0]이 mid.data[0] 보다 클 때
while True:
if iterator.next is None: # iterator 뒤에 노드가 없을 경우
self.insert(iterator, data) # 리스트의 맨 뒤에 바로 추가
break
elif data[0].lower() > iterator.next.data[0].lower(): # 새로 추가될 단어가 iterator.next 보다 클 경우 (알파벳 순서상 뒤에 위치)
iterator = iterator.next # iterator 위치 한칸 뒤로 이동
elif data[0].lower() < iterator.next.data[0].lower(): # 새로 추가될 단어가 iterator.next 보다 작을 경우 (알파벳 순서상 앞에 위치)
self.insert(iterator, data) # iterator 뒤에 해당 단어 삽입
break
elif data[0].lower() == iterator.next.data[0].lower(): # 새로 추가될 단어가 iterator.next 와 같을 경우 (lower() 때문에)
if data[0] < iterator.next.data[0]: # 원본으로 복구 시켜 비교했을때, 새로 추가될 단어가 iterator.next 보다 작을 경우 (알파벳 순서상 앞에 위치)
self.insert(iterator, data) # iterator 뒤에 해당 단어 삽입
break
elif data[0] > iterator.next.data[0]: # 원본으로 복구 시켜 비교했을때, 새로 추가될 단어가 iterator.next 보다 클 경우 (알파벳 순서상 뒤에 위치)
self.insert(iterator.next, data) # iterator.next 뒤에 해당 단어 삽입
break
self.pos += 1 # 링크드 리스트의 오른쪽으로 이동할 경우엔 pos에 +1
"""mid 위치 설정 : pos의 값이 0일때의 mid의 위치가 링크드 리스트의 중앙이다."""
if self.pos == 2: # pos가 2가 되면
self.mid = self.mid.next # mid를 오른쪽으로 이동
self.pos = 0 # pos값 다시 0으로
elif self.pos == -2: # pos가 -2가 되면
self.mid = self.mid.prev # mid를 왼쪽으로 이동
self.pos = 0 # pos값 다시 0으로
"""링크드 리스트에서 원하는 단어를 탐색하기 위한 메소드"""
def search(self, target):
iterator = self.mid # 리스트의 중간부터 찾고자 하는 단어(target) 탐색 시작
while True:
if target.lower() == iterator.data[0].lower(): # 찾고자 하는 단어가 iterator 와 같을 경우 (lower() 때문에)
if target == iterator.data[0]: # 원본으로 복구 시켜 비교했을때, 찾고자 하는 단어가 iterator 와 같을 경우
print(iterator.data[1]) # iterator에 해당하는 단어의 뜻 출력
break
elif target < iterator.data[0]: # 찾고자 하는 단어가 iterator 보다 작을 경우 (알파벳 순서상 앞에 위치)
iterator = iterator.prev # iterator 위치 한칸 앞으로 이동
if target == iterator.data[0]: # 찾고자 하는 단어가 iterator(이동 한 위치) 와 같을 경우
print(iterator.data[1]) # iterator에 해당하는 단어의 뜻 출력
break
else: # 찾고자 하는 단어가 없을 경우
self.newWord(target) # newWord 메소드 호출
break
else: # 찾고자 하는 단어가 iterator 보다 클 경우 (알파벳 순서상 뒤에 위치)
iterator = iterator.next # iterator 위치 한칸 뒤로 이동
if target == iterator.data[0]: # 찾고자 하는 단어가 iterator(이동 한 위치) 와 같을 경우
print(iterator.data[1]) # iterator에 해당하는 단어의 뜻 출력
break
else: # 찾고자 하는 단어가 없을 경우
self.newWord(target) # newWord 메소드 호출
break
elif target.lower() < iterator.data[0].lower(): # 찾고자 하는 단어가 iterator 보다 작을 경우 (알파벳 순서상 앞에 위치)
if iterator.prev is None: # iterator 앞에 노드가 없을 경우
self.newWord(target) # newWord 메소드 호출
break
elif target.lower() > iterator.prev.data[0].lower(): # 찾고자 하는 단어가 iterator.prev 보다 클 경우 (알파벳 순서상 뒤에 위치)
self.newWord(target) # newWord 메소드 호출 -> 튕겨져 나온 경우 이므로 찾는 단어가 없다는 의미이다
break
else: # 위의 경우들에 해당하지 않는다면
iterator = iterator.prev # iterator 위치 한칸 앞으로 이동
else: # 찾고자 하는 단어가 iterator 보다 클 경우 (알파벳 순서상 뒤에 위치)
if iterator.next is None: # iterator 뒤에 노드가 없을 경우
self.newWord(target) # newWord 메소드 호출
break
elif target.lower() < iterator.next.data[0].lower(): # 찾고자 하는 단어가 iterator.next 보다 작을 경우 (알파벳 순서상 앞에 위치)
self.newWord(target) # newWord 메소드 호출 -> 튕겨져 나온 경우 이므로 찾는 단어가 없다는 의미이다
break
else: # 위의 경우들에 해당하지 않는다면
iterator = iterator.next # iterator 위치 한칸 뒤로 이동
"""찾고자 하는 단어가 없을 경우, 해당 단어를 추가하는 메소드"""
def newWord(self, inputWord):
print('찾을 수 없는 단어입니다. 뜻을 추가하세요(추가하지 않으려면 공백)')
inputMean = input('> ')
if inputMean == '':
pass
else:
newData = [inputWord, inputMean]
self.makeDict(newData)
print(newData[0], newData[1], '가 추가되었습니다.(총 %d개 단어)' % self.size)
f = open('randdict_utf8.TXT', 'r')
dict = dbList() # randdict의 단어를 가져와 담아 줄 링크드 리스트
while True:
line = f.readline()
if line == '':
break
word = line[:line.find(':') - 1] # 단어
mean = line[line.find(':') + 2:-1] # 단어의 뜻
data = [word, mean] # [단어, 뜻]
dict.makeDict(data)
f.close()
while True:
target = input(">>")
dict.search(target)
프로그램을 실행해본 결과 내 의도대로 이중 연결 리스트를 사용했기 때문인지는 모르겠지만
단어를 검색하는 작업과, 존재하지 않는 단어를 연결리스트상에 추가하고,
이를 다시 탐색하는 작업은 매우 빠르게 수행되었다.
그러나 데이터의 양이 꽤 많아서인지 단어장의 단어들을 가져와
정렬된 상태로 연결 리스트를 만드는 과정에는 꽤 많은 시간이 소요되었다. (대략 1분 30초정도)
따라서 해당 부분에 대한 보완에 대해서도 좀 더 생각해봐야 할 필요가 있다고 느꼈다.