利用hashtable和time函數加速Lisp程序

   程序功能是從一個英文文本中得到單詞表,再得到押韻詞表。即輸出可能這樣開始:
a ameoeba alba samba marimba...
這樣結束:
...megahertz gigahertz jazz buzz fuzz
有了這麼一個表,詩人會爽很多。

算法:得到單詞表後,先把單詞反序,用sort函數對所有反序單詞排序,排好後再反序。

     我嘰裏呱啦地寫完了,用了630KB的2100行的文本來測試一下。time一下main函數,30多秒……現在用瞭如題的兩個工具,程序跑到了0.08s!

首先,我處理重複單詞的算法超級低效,先是在read-word函數裏,不管是否當前單詞已記錄,都把當前單詞記錄下來,然後:
(setf words (delete-duplicates words :test #'equal :end n))
用delete-duplicates函數只保留相同單詞的最後一個。
該函數的工作原理是兩個兩個比較,然後把前者丟棄掉,如果:from-end是true的話,那把後者丟棄掉。顯然的是,它要比較很多,如果一個單詞重複很多次的話,那要經過很多輪的比較,元素移動很多。具體是如何經過很多輪的、爲什麼很慢的,我也不懂,望高手指點。
如果我在添加新單詞的時候:
 
(when (not (find word words :test #'string-equal))
  (vector-push-extend word words 10000)
  (incf n))
單詞不存在的時候才加入。這樣一來,程序就飛到了4s。

我是怎麼知道delete-duplicates這裏慢了呢?高手可以靜態分析……當然,很容易猜錯,分析失誤。

(defun main ()
   (defvar n)
   (time (setf n (read-word "source.txt")))
   (time (setf words (delete-duplicates words :test #'equal :end n)))
   (time (reverse-words words))
   (time (sort words #'string< #'nreverse))
   (time (reverse-words words))
   (time (write-word "rhymes.txt")))

  這樣子給每個函數time一下。在c++中,即是clock()/CLOCKS_PER_SEC。有了它,就是爲啥“過早優化是一切罪惡的根源”。非常令我驚訝的是,一開始總共37s的時間,36s耗在 delete-duplicates上。按道理來說,我應該用36/37的優化時間都花在這部分。如果過早優化的話,一方面浪費時間在無關痛癢的優化上;另一方面,過早優化,代碼更復雜,使得後面越來越難以修改,也容易多錯誤。所以,面對一問題,應該儘快地搗鼓出簡單的第一版,之後再考慮優化。
接下來,用hash-table來記錄單詞,像這樣

(defparameter ht (make-hash-table :test #'equal :size 10000))
(when (not (gethash word ht)) 
  (setf (gethash word ht) t)
  (vector-push-extend word words 10000))
這樣就優化到了0.08s。

另外,在處理的過程中,不希望有那麼多中間數組,故用map-into,像這樣

(map-into seq fn seq)
函數fn對seq的每一個元素調用後結果保存到seq中。
希望數組v每個數都加1可寫成:

(setf v (map-into v #'1+ v))

所以,最終我把反序、排序、再反序、輸出,寫成了這樣:

(defun xform (fn seq)
	(map-into seq fn seq))

(defun write-word (to)
	(with-open-file (str to :direction :output
				:if-exists :supersede)
	  (map nil #'(lambda (x)
			(fresh-line str)
			(princ x str))
		  (xform #'nreverse
			(sort (xform #'nreverse words)
			      #'string<)))))
事實上,這照搬了《Ansi Common Lisp》的代碼。

代碼見:https://github.com/lzwjava/rhymes
 

發佈了74 篇原創文章 · 獲贊 25 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章