ليسب لتدريس الحاسوب كتابة
هذه المنشورة قد كتبت في الأصل باللغة الصينية ونشرت على CSDN، https://blog.csdn.net/lzw_java/article/details/11599993
أكبر جزء من الكود والفكر مبني على “Ansi Common Lisp” P138~P141.
المسألة: كيف يمكن ل الكمبيوتر أن يولد نصاً عشوائياً ولكن قابلاً للقراءة بناءً على نص إنجليزي؟ على سبيل المثال:
تقدر الجمعية الوطنية للتمويل الاستثماري أن الثروة المرتبطة بصفقة كبيرة من الإنفاق من قبل القوانين التي ستنفق بعضها البعض من الأسباب الرئيسية لهذه المشاريع.
هذا النص العشوائي تم إنشاؤه بواسطة الكمبيوتر بعد تعلم بعض مقالات بول غراهام. يتوسع إلى جملة من الكلمة “Venture”. بشكل مفاجئ، فإن النص غالباً ما يكون قابلاً للقراءة.
الخوارزمية: تسجيل الكلمات التي تظهر بعد كل كلمة وعدد مرات ظهورها. على سبيل المثال، إذا ظهر “I leave” 5 مرات في النص الأصلي و “I want” 3 مرات، و “I” لم يكن يظهر قبل أي كلمة أخرى، عندها عند إنشاء النص العشوائي، عند ظهور “I” هناك احتمال 5/8 اختيار “leave” كالكلمة التالية. إذا تم اختيار “leave”، ثم فحص الكلمات التي ظهرت بعدها وكرر العملية.
الآن، دعونا حل المشكلة باستخدام Lisp.
يمكن أن تكون لisp من نوع الرموز تسجّل مختلف الأiscal والعلامات التعبيرية جيداً، فسنستخدمها للتسجيل. سنستعمل الجدول الراسمي المتكامل لإنشاء القائمة:
(defparameter *words* (make-hash-table :size 10000))
كيف نقوم بإنشاء القائمة؟
(let ((prev '|.|))
(defun see (sym)
(let ((pair (assoc sym (gethash prev *words*))))
(if pair
(incf (cdr pair))
(push (cons sym 1) (gethash prev *words*))))
(setf prev sym)))
الكلمة الحالية هي المفتاح، وقائمة assoc هي القيمة تحت ذلك المفتاح. على سبيل المثال، تحت “I” لدينا ( (|leave| . 5) (|want| . 3) ). إذا لم تكن الكلمة موجودة، ثم (word . 1).
كيف نقوم بأخذ كلمة عشوائية؟
(defun random-word (word ht)
(let* ((choices (gethash word ht))
(x (random (reduce #'+ choices :key #'cdr))))
(dolist (pair choices)
(decf x (cdr pair))
(if (minusp x)
(return (car pair))))))
هنا، يتم استخدام وظيفة التsummary بطريقة ذكية.
الآن، دعونا نفكر كيف يمكن أن نمدد كلمة معينة إلى جملة من كلا الجانبين؟ 1) عكس النص للحصول على قائمة معكوسة، أي “I leave, I want” يصبح “leave I, want I”.
2) عكس الجدول الراسمي للحصول على جدول راسمي آخر، حيث الكلمة الأخيرة هي المفتاح، وجميع الكلمات السابقة والمعدلات تشكل قائمة assoc.
3) حاول حظك، ابدأ في تمديد الجملة من علامة عبارات حتى تظهر الكلمة المعينة.
استخدمت الطريقة الثانية:
(defparameter *r-words* (make-hash-table :size 10000))
(defun push-words (w1 w2 n)
(push (cons w2 n) (gethash w1 *r-words*)))
(defun get-reversed-words () ; a cat -> cat a
(maphash #'(lambda (k lst)
(dolist (pair lst)
(push-words (car pair) k (cdr pair))))
*words*))
عبور الجدول الراسمي الأصلي، ثم إدراج كل زوج من الكلمات في جدول راسمي آخر مع ترتيبهم معكوساً. هنا هو الكود لإنتاج الجمل المتسقة تلقائياً:
(defparameter *words* (make-hash-table :size 10000))
(defconstant maxword 100)
(defparameter nwords 0)
(defconstant debug nil)
(let ((prev '|.|))
(defun see (sym)
(incf nwords)
(let ((pair (assoc sym (gethash prev *words*))))
(if pair
(incf (cdr pair))
(push (cons sym 1) (gethash prev *words*))))
(setf prev sym)))
(defun check-punc (c) ; char to symbol
(case c
(#\. '|.|) (#\, '|,|)
(#\; '|;|) (#\? '|?|)
(#\: '|:|) (#\! '|!|)))
(defun read-text (pathname)
(with-open-file (str pathname :direction :input)
(let ((buf (make-string maxword))
(pos 0))
(do ((c (read-char str nil 'eof)
(read-char str nil 'eof)))
((eql c 'eof))
(if (or (alpha-char-p c)
(eql c #\'))
(progn
(setf (char buf pos) c)
(incf pos))
(progn
(unless (zerop pos)
(see (intern (subseq buf 0 pos)))
(setf pos 0))
(let ((punc (check-punc c)))
(if punc
(see punc)))))))))
(defun print-ht (ht)
(maphash #'(lambda (k v)
(format t "~A ~A~%" k v))
ht))
(defparameter *r-words* (make-hash-table :size 10000))
(defun push-words (w1 w2 n)
(push (cons w2 n) (gethash w1 *r-words*)))
(defun get-reversed-words () ; a cat -> cat a
(maphash #'(lambda (k lst)
(dolist (pair lst)
(push-words (car pair) k (cdr pair))))
*words*))
(defun print-a-word (word ht)
(maphash #'(lambda (k lst)
(if (eql k word)
(format t "~A ~A~%" k lst)))
ht))
(if debug
(print-a-word '|leave| *r-words*))
(defun punc-p (sym) ; symbol to char, nil when fails.
(check-punc (char (symbol-name sym) 0)))
(defun random-word (word ht)
(let* ((choices (gethash word ht))
(x (random (reduce #'+ choices :key #'cdr))))
(dolist (pair choices)
(decf x (cdr pair))
(if (minusp x)
(return (car pair))))))
(defun gen-former (word str)
(let ((last (random-word word *r-words*)))
(if (not (punc-p last))
(progn
(gen-former last str)
(format str "~A " last)))))
(defun gen-latter (word str)
(let ((next (random-word word *words*)))
(format str "~A " next)
(if (not (punc-p next))
(gen-latter next str))))
;(gen-latter '|leave| t)
(defun get-a-word (ht) ; get a random word
(let ((x (random nwords)))
(maphash #'(lambda (k v)
(dolist (pair v)
(decf x (cdr pair))
(if (minusp x)
(return-from get-a-word (car pair)))))
ht)))
;(get-a-word *words*)
(defun gen-sentence (word str)
(gen-former word str)
(format str "~A " word)
(gen-latter word str))
(defun test ()
(setf nwords 0)
(read-text "essay.txt")
(get-reversed-words)
(let ((word (get-a-word *words*)))
(print word)
(gen-sentence word t)))
(test)