使用Python開發一個英文句子分詞程序,把一段英文句子切分爲每個單詞。不能導入任何官方的或者第三方的庫,也不能使用字符串的split()方法。python
對於只有單詞和空格,不含其餘符號的英語句子,可使用空格來切分單詞。因而對於句子I am kingname
, 一個字符一個字符的進行遍歷。首先遍歷到I
,發現它是一個字母,因而把它存到一個變量word
中,而後遍歷到空格,因而把變量word
的值添加到變量word_list
中,再把word
清空。接下來遍歷到字母a
,又把a
放到變量word
中。再遍歷到m
,發現它仍是一個字母,因而把字母m
拼接到變量word
的末尾。此時變量word
的值爲am
。再遍歷到第二個空格,因而把word
的值添加到word_list
中,清空word
。app
最後,因爲第三個單詞kingname
的末尾沒有空格,因此須要手動把它添加到列表word_list
中。編碼
完整的代碼以下:spa
def split(target):
if not target:
return []
word_list = []
word = ''
for letter in target:
if letter == ' ':
word_list.append(word)
word = ''
else:
word += letter
return word_list
if __name__ == '__main__':
sentence = 'I am kingname'
result_word_list = split(sentence)
print(result_word_list)
複製代碼
運行效果以下圖所示。3d
如今不只僅只有單詞和空格,還有逗號和句號。有這樣一個句子:"I am kingname,you should remember me."若是使用上一小節的程序,那麼代碼就會出現問題,以下圖所示。code
其中,"kingname,you"應該是兩個單詞,可是在這裏變成了一個單詞。因此如今不只遇到空格要進行切分,遇到逗號句號還須要進行切分。那麼對代碼作一些修改,變成以下代碼:cdn
def split(target):
if not target:
return []
word_list = []
word = ''
for letter in target:
if letter in [' ', ',', '.']:
word_list.append(word)
word = ''
else:
word += letter
if word:
word_list.append(word)
return word_list
if __name__ == '__main__':
sentence = 'I am kingname,you should remember me.'
result_word_list = split(sentence)
print(result_word_list)
複製代碼
如今運行起來看上去沒有問題了,以下圖所示。blog
然而,有些人寫英文的時候喜歡在標點符號右側加一個空格,例如:"I am kingname, you should remember me."這樣小小的一修改,上面的代碼又出問題了,以下圖所示。開發
分詞出來的結果裏面憑空多出來一個空字符串。爲了解決這個問題,再加一層判斷,只有發現word
不爲空字符串的時候才把它加入到word_list
中,代碼繼續修改:rem
def split(target):
if not target:
return []
word_list = []
word = ''
for letter in target:
if letter in [' ', ',', '.']:
if not word:
continue
word_list.append(word)
word = ''
else:
word += letter
if word:
word_list.append(word)
return word_list
if __name__ == '__main__':
sentence = 'I am kingname, you should remember me.'
result_word_list = split(sentence)
print(result_word_list)
複製代碼
代碼看起來又能夠正常工做了。以下圖所示。
標點符號可不只僅只有逗號句號。如今又出現了冒號分號雙引號感嘆號問號等等雜七雜八的符號。英文句子變爲:"I am kingname, you should say: "Kingname Oba" to me, will you?"
使用上面的代碼,發現運行起來又出問題了。以下圖所示。
爲了能覆蓋到全部的標點符號,如今修改一下邏輯。原來是「遇到空格/逗號/句號」就把word
放到word_list
中。如今要改成「若是當前字符不是字母,就把word
放到word_list
中」。因而代碼進一步作修改:
constant = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
def split(target):
if not target:
return []
word_list = []
word = ''
for letter in target:
if letter not in constant:
if not word:
continue
word_list.append(word)
word = ''
else:
word += letter
if word:
word_list.append(word)
return word_list
if __name__ == '__main__':
sentence = 'I am kingname, you should say: "Kingname Oba" to me, will you?'
result_word_list = split(sentence)
print(result_word_list)
複製代碼
代碼修改之後又能夠正常工做了,其運行效果以下圖所示:
若是雙引號包含的句子裏面還須要用到引號,那麼就須要在內部使用單引號。例若有這樣一個句子:「I am kingname, you should say: "Kingname Oba, I always remember your motto: 'kingname is genius'" to me, will you?」
使用前面的代碼,運行起來彷佛沒有問題,以下圖所示。
可是,單引號還有其餘用途——有人喜歡把兩個單詞合併成一個單詞,例如:
在這種狀況下,就應該把單引號鏈接的兩部分看做是一個單詞,不該該把它們切開。
若是句子變成:I'm kingname, you should say: "Kingname Oba, I always remember your motto: 'kingname's genius'" to me, won't you?
繼續使用上面的代碼,就發現返回的單詞列表又不對了。以下圖所示。
要解決這個問題,就須要肯定單引號具體是作普通的引號來使用,仍是放在縮寫裏使用。
做爲普通單引號使用的時候,若是是前單引號,那麼它的左邊一定不是字母,若是做爲後單引號,那麼它的右邊一定不是字母。而縮寫裏面的單引號,它左右兩側一定都是字母。而且須要注意,若是是句子裏面第一個符號就是單引號,那麼此時它左邊沒有字符;若是句子裏面最後一個符號是單引號,那麼它右邊沒有字符,此時若是使用下標來查找,就須要小心下標越界。
對代碼進一步修改:
constant = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
def split(target):
if not target:
return []
word_list = []
word = ''
for index, letter in enumerate(target):
if letter not in constant and letter != "'":
if not word:
continue
word_list.append(word)
word = ''
elif letter == "'":
if 0 < index < len(target) - 1 \
and target[index - 1] in constant \
and target[index + 1] in constant:
word += letter
else:
word += letter
if word:
word_list.append(word)
return word_list
if __name__ == '__main__':
sentence = '''I'm kingname, you should say: "Kingname Oba, I always remember your motto: 'kingname's genius'" to me, won't you?'''
result_word_list = split(sentence)
for word in result_word_list:
print(word)
複製代碼
如今代碼又能夠成功運行了,以下圖所示。
可是請細看代碼,如今已經混亂到難以閱讀難以理解了。若是再增長一個連字符又怎麼改?若是單詞內部出現了兩個單引號怎麼改?這種爲了增長一個功能,要把不少不相干代碼也進行修改的編碼方式,相信能夠擊中不少初學者甚至是很多自稱爲軟件工程師的人。
根據分詞邏輯,遇到各類符號應該怎麼處理,畫一個分詞的狀態轉移圖出來。
從這個圖上能夠看出來,其實程序只須要知道當前是什麼狀態,以及遇到什麼字符須要轉移到什麼狀態就能夠了。沒有必要知道本身是從哪一個狀態轉移過來的,也沒有必要知道和本身不相干的其餘狀態。
舉一個例子:I'm kingname, you should say: "Kingname Oba, I always remember your motto: 'kingname's genius'" to me, won't you?
這個句子中,should
這個單詞就是處於「單詞狀態」。它不在單引號內部,它也不是一個縮寫。當咱們對句子每一個字符進行遍歷的時候,遍歷到「should」的「s」時進入「單詞狀態」,在單詞狀態,只須要關心接下來過來的下一個字符是什麼,若是是字母,那依然是單詞狀態,把字母直接拼接上來便可。若是是單引號,那麼進入「單引號在單詞中狀態」。至於「單引號在單詞中狀態」有什麼邏輯,單詞狀態的代碼根本不須要知道。這就像是接力賽,我把棒交給下一我的,個人任務就作完了,下一我的是跑到終點仍是爬到終點,都和我沒有關係。
這就是有限狀態機FSM的原理。
根據這個原理,使用狀態和轉移關係來改寫代碼,就可讓代碼的邏輯變得很是清晰。改進之後的代碼以下:
class Spliter(object):
def __init__(self):
self.constant = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
self.state = '初始狀態'
self.word = ''
self.word_list = []
self.state_dict = {'初始狀態': self.parse_init,
'單詞狀態': self.parse_word,
'單引號在單詞中狀態': self.parse_contraction}
def parse_init(self, letter):
if letter in self.constant:
self.state = '單詞狀態'
self.word += letter
def parse_word(self, letter):
if letter in self.constant:
self.word += letter
elif letter == "'":
self.state = '單引號在單詞中狀態'
self.word += "'"
else:
self.word_list.append(self.word)
self.state = '初始狀態'
self.word = ''
def parse_contraction(self, letter):
if letter in self.constant:
self.word += letter
self.state = '單詞狀態'
else:
self.word_list.append(self.word[:-1])
self.word = ''
self.state = '初始狀態'
def split(self, target):
for letter in target:
self.state_dict[self.state](letter)
return self.word_list
if __name__ == '__main__':
spliter = Spliter()
sentence = '''I'm kingname, you should say: "Kingname Oba, I always remember your motto: 'kingname's genius'" to me, won't you?'''
print(spliter.split(sentence))
複製代碼
代碼運行效果以下圖所示。
須要注意的是,圖中的代碼只是使用了有限狀態機的原理,而並不是一個有限狀態機。
個人公衆號:未聞Code