從上一節的洗禮你應該已經越來越瞭解Python這個程式語言了,不過這部份我們要介紹更多重要的元素!它將會在未來實際執行自然語言處理時扮演關鍵角色。
Lists(列表或串列)
文本到底是什麼?某些程度上,它只是一些字詞與符號排列而成的一頁頁序列;某星程度上,整個文本只是由每個章節組成的序列,每個章節又只是由一些段落所組成的序列...。總之,要用電腦來搞定文本的首要概念就是把它當成:一個由文字與符號組成的玩意兒!一開始我們來先利用文本1(Moby Dick)的開頭來進行測試吧:>>> sent1 = ['Call', 'me', 'Ishmael', '.'] >>> |
我們仔細看一下這個例子,首先取了一個名字(sent1)之後,接著在等號後面出現了一個中括號,這個中括號告訴我們sent1的資料結構是一個串列(list),這個串列中有四個字串,每個字串用單引號包覆著,並用逗號隔開。我們可以利用sent1來測試一些我們學過的東西,像是敲名字可以在顯示一次內容、計算長度以及運用之前建立的函數來計算語意多樣性:
>>> sent1 ['Call', 'me', 'Ishmael', '.'] >>> len(sent1) 4 >>> lexical_diversity(sent1) 1.0 >>> |
事實上,NLTK已經為你準備了一系列的sent(從sent1, sent2...sent9),你只需要敲入名稱就可以隨時察看並拿來測試(如果Python直譯器告訴你變數尚未定義的話,請記得再把NLTK導入一次:from nltk.book import *)。
>>> sent2 ['The', 'family', 'of', 'Dashwood', 'had', 'long', 'been', 'settled', 'in', 'Sussex', '.'] >>> sent3 [ 'In', 'the', 'beginning', 'God', 'created', 'the', 'heaven', 'and', 'the', 'earth', '.'] >>> |
利用Python加法,你可以發現串列是非常有趣的!他將會把兩個串列連接起來,稱為串接。
>>> ['Monty', 'Python'] + ['and', 'the', 'Holy', 'Grail'] ['Monty', 'Python', 'and', 'the', 'Holy', 'Grail'] |
偷懶一點,只需要打入名稱(假如名稱都是串列)也可以直接連起來:
>>> sent4 + sent1 ['Fellow', '-', 'Citizens', 'of', 'the', 'Senate', 'and', 'of', 'the', 'House', 'of', 'Representatives', ':', 'Call', 'me', 'Ishmael', '.'] >>> |
那如果要把新東西加入在串列該怎麼做?就使用append()指令吧,把意欲新增的項目放在括弧中當成參數,就可以把它新增到串列當中囉!
>>> sent1.append("Some") >>> sent1 ['Call', 'me', 'Ishmael', '.', 'Some'] >>> |
善用串列的索引
如同我們前面看到的,一個文本也不過是裝在一些中括號的引號文字集合而已。所以我們可以輕易查詢一個文本的長度,如len(text1),或是計算某個字的出現次數,如
稍微靜下來思考看看,如果我們已經可以確切掌握文本中每個依照順序出現的文字(也可能是符號),那是不是可以輕易地去抓出該文本的第1個字、第173個字或甚至第14278個字?這是當然的!Python在建立串列時就已經把索引值給記錄起來了,我們試著把文本中的第173個字抓出來試試看,只需要把索引值裝在中括號即可:
>>> text4[173] 'awaken' >>> |
也可以反過來,假定想知道某個字出現在文本的位置,只要使用index()指令即可:
>>> text4.index('awaken') 173 >>> |
用索引來存取文本中的文字是超級實用的方法,因為他可以掌握整個串列中的每一個元素。此外,Python還允許我們提取子串列的方式,叫做切片(slicing),它可以擷取任意大小串列中的某一段特定的內容:
>>> text5[16715:16735] ['U86', 'thats', 'why', 'something', 'like', 'gamefly', 'is', 'so', 'good', 'because', 'you', 'can', 'actually', 'play', 'a', 'full', 'game', 'without', 'buying', 'it'] >>> text6[1600:1625] ['We', "'", 're', 'an', 'anarcho', '-', 'syndicalist', 'commune', '.', 'We', 'take', 'it', 'in', 'turns', 'to', 'act', 'as', 'a', 'sort', 'of', 'executive', 'officer', 'for', 'the', 'week'] >>> |
索引的原理有許多微妙之處可以慢慢談,我們建立一個串列來解釋一番:
>>> sent = ['word1', 'word2', 'word3', 'word4', 'word5', ... 'word6', 'word7', 'word8', 'word9', 'word10'] >>> sent[0] 'word1' >>> sent[9] 'word10' >>> |
從上面的例子可以清楚瞭解到,索引的起始是「0」,sent[0]才是表示「word1」,而最後的元素word10則是以「sent[9]」表示。這裡由很單純,因為當Python在從電腦記憶體中存取串列的內容時,從第一個元素中就已經告訴電腦,接下來還有多少元素(多少步)要走,因此第0步(不用走)就表示第1個元素。如果你不小心或故意輸入超出範圍的索引直就會像下面這樣,出現錯誤訊息!這一次不是語法的錯誤(語法正確),而是執行時的錯誤,就如同Traceback的訊息所回報,是索引的錯誤:超出串列範圍。
>>> sent[10] Traceback (most recent call last): File "<stdin>", line 1, in ? IndexError: list index out of range >>> |
現在!繼續測試切片的功能,像是現在來測試切片5:8,則表示提取出索引值5、6、7的內容:
>>> sent[5:8] ['word6', 'word7', 'word8'] >>> sent[5] 'word6' >>> sent[6] 'word7' >>> sent[7] 'word8' >>> |
一般來講,「切片m:n」可以直接解讀成「提取元素m...到n-1」!下面的例子中,我們試著刪掉起始的切片值(表示從頭開始),或是刪掉結束的切片值(表示到最後)。
>>> sent[:3] ['word1', 'word2', 'word3'] >>> text2[141525:] ['among', 'the', 'merits', 'and', 'the', 'happiness', 'of', 'Elinor', 'and', 'Marianne',',', 'let', 'it', 'not', 'be', 'ranked', 'as', 'the', 'least', 'considerable', ',','that', 'though', 'sisters', ',', 'and', 'living', 'almost', 'within', 'sight', 'of','each', 'other', ',', 'they', 'could', 'live', 'without', 'disagreement', 'between','themselves', ',', 'or', 'producing', 'coolness', 'between', 'their', 'husbands', '.','THE', 'END'] >>> |
我們也可以透過索引來修改串列的元素,同時也可以用新的資料來針對切片進行替換。像是在下面的例子裡,把原本10的元素換成了4個,再輸入原本的索引值時反而出錯了!
>>> sent[0] = 'First' >>> sent[9] = 'Last' >>> len(sent) 10 >>> sent[1:9] = ['Second', 'Third'] >>> sent ['First', 'Second', 'Third', 'Last'] >>> sent[9] Traceback (most recent call last): File "<stdin>", line 1, in ? IndexError: list index out of range >>> |
變數
從1.1開始,我們就使用了各種代號text1、text2來存取文本,當然也包括了剛剛用的一系列sent也是: >>> sent1 = ['Call', 'me', 'Ishmael', '.'] >>> |
上面這一行顯示了:「變數=表達式」的意涵,Python會執行表達式並把結果存給變數,而這個動作就叫做宣告(assignment)。執行後Python直譯器不會產生任何輸出,想要檢視其內容則必須輸入變數名稱後才行。變數名稱可以隨便你取(原則上),主要的限制就是開頭必須是字母,剩下你要加數字、底線都可以。以下的範例可以提供參考:
>>> my_sent = ['Bravely', 'bold', 'Sir', 'Robin', ',', 'rode', ... 'forth', 'from', 'Camelot', '.'] >>> noun_phrase = my_sent[1:4] >>> noun_phrase ['bold', 'Sir', 'Robin'] >>> wOrDs = sorted(noun_phrase) >>> wOrDs ['Robin', 'Sir', 'bold'] >>> |
記得喔!排序的時候,大寫字母是會比小寫優先的!
>>> not = 'Camelot' File "<stdin>", line 1 not = 'Camelot' ^ SyntaxError: invalid syntax >>> |
變數也經常被拿來當作運算時的中介角色,像是我們之前使用過的「len(set(text1))」就可以被分解成:
>>> vocab = set(text1) >>> vocab_size = len(vocab) >>> vocab_size 19317 >>> |
最後在叮嚀你一次!一定要小心選擇你在Python中使用的這些名稱,開頭必需要是字母,接著可以選擇性地加入數字,如abc123是可以的,但是123abc則是語法錯誤的!名稱本身也是相當敏感的,myVar跟myvar兩個是不同的東西(即使只差一個字母的大小寫而已)。使用底線是沒有問題的,如my_var,但是使用連字號就是錯誤的,如my-var,因為連字號是「減號」的意思!
字串
有些我們之前拿來處理串列的方法也可以拿到字串這邊來用,像是宣告一個字串變數,或是在字串中使用索引或切片來指定內容:>>> name = 'Monty' >>> name[0] 'M' >>> name[:4] 'Mont' >>> |
加法與乘法也可以拿來處理字串:
>>> name * 2 'MontyMonty' >>> name + '!' 'Monty!' >>> |
另外更可以把串列中的元素連結起來變成字串,也可以反過來把字串拆解成串列喔!
>>> ' '.join(['Monty', 'Python']) 'Monty Python' >>> 'Monty Python'.split() ['Monty', 'Python'] >>> |
有關於更多更詳細的字串探討,將會在第三章詳細討論。目前我們已經對串列與字串有了一些基本概念,也可以開始著手進行一些自然語言分析的工作囉!
下一節:1.3 開始計算語言吧:簡單的統計
沒有留言:
張貼留言