1

プログラムは、以下のように文字列を再フォーマットする必要があります。

例: (game-print '(THIS IS A SENTENCE. WHAT ABOUT THIS? おそらく.))

これは文です。これはどうですか?おそらく。

しかし、何かが間違っています (Lisp のネストが `max-lisp-eval-depth を超えています)。その理由がわかりません。このコードは、実際には本の「Land of Lisp」の 97 ページからのものです。元のコードは Common Lisp で書かれています。elispで書き直したいです。tweak-text の最後の 2 つの引数は、キャプテンとリテラルを意味します。

(defun tweak-text (lst caps lit)
  (when lst
    (let ((item (car lst))
          (rest (cdr lst)))
      (cond ((eql item ?\ ) (cons item (tweak-text rest caps lit)))
            ((member item '(?\! ?\? ?\.)) (cons item (tweak-text rest t lit)))
            ((eql item ?\") (tweak-text rest caps (not lit)))
            (lit (cons item (tweak-text rest nil lit)))
            (caps (cons (upcase item) (tweak-text rest nil lit)))
            (t (cons (downcase item) (tweak-text rest nil nil)))))))

(defun game-print (lst)
  (print (coerce (tweak-text (coerce (prin1-to-string lst) 'list) t nil) 'string)))

(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))

common lisp で書かれた元のコード。

(defun tweak-text (lst caps lit)
  (when lst
    (let ((item (car lst))
          (rest (cdr lst)))
      (cond ((eql item #\space) (cons item (tweak-text rest caps lit)))
            ((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
            ((eql item #\") (tweak-text rest caps (not lit)))
            (lit (cons item (tweak-text rest nil lit)))
            (caps (cons (char-upcase item) (tweak-text rest nil lit)))
            (t (cons (char-downcase item) (tweak-text rest nil nil)))))))

(defun game-print (lst)
    (princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string lst)) 'list) t nil) 'string))
    (fresh-line))
4

5 に答える 5

4

どちらの場合も、非終端再帰があるため、O(length(lst)) スタック スペースを使用しています。明らかに、システムは使用できるスタックスペースを制限する可能性があり、実際に emacs でこの制限に達します。(さて、emacs では、max-lisp-eval-depth を変更することで制限を増やすことができますが、これでは根本的な問題は解決されません)。

解決策は、再帰の代わりに反復を使用することです。

しかし、まず、emacs で次のように記述します。

(defun character (x)
  "common-lisp: return the character designated by X."
  (etypecase x
    (integer x)
    (string (aref x 0))
    (symbol (aref (symbol-name x) 0))))

(defun string-trim (character-bag string-designator)
  "common-lisp: returns a substring of string, with all characters in \
character-bag stripped off the beginning and end."
  (unless (sequencep character-bag)
    (signal 'type-error  "expected a sequence for `character-bag'."))
  (let* ((string (string* string-designator))
         (margin (format "[%s]*" (regexp-quote
                                  (if (stringp character-bag)
                                      character-bag
                                      (map 'string 'identity character-bag)))))
         (trimer (format "\\`%s\\(\\(.\\|\n\\)*?\\)%s\\'" margin margin)))
    (replace-regexp-in-string  trimer "\\1" string)))

(require 'cl)

CL と elisp の両方に対して単一の関数を記述できるようにします。

(defun tweak-text (list caps lit)
  (let ((result '()))
    (dolist (item list (nreverse result))
      (cond ((find item " !?.")          (push item result))
            ((eql item (character "\"")) (setf lit (not lit)))
            (lit                         (push item result)
                                         (setf caps nil))
            (caps                        (push (char-upcase item) result)
                                         (setf caps nil))
            (t                           (push (char-downcase item) result)
                                         (setf caps nil
                                               lit nil))))))

(defun game-print (list)
  (princ (coerce (tweak-text (coerce (string-trim "() " (prin1-to-string list)) 'list)
                             t nil)
                 'string))
  (terpri))

それで:

(game-print '(not only does this sentence have a "comma," it also mentions the "iPad."))

emacs で:

prints:   Not only does this sentence have a comma, it also mentions the iPad.
returns:  t

Common Lisp では:

prints:   Not only does this sentence have a comma, it also mentions the iPad.
returns:  nil

さて、一般に文字列を処理するためにリストを使う意味はほとんどありません。emacs lisp と Common Lisp の両方が、シーケンスと文字列を直接扱うための強力なプリミティブを持っています。

于 2012-06-14T05:07:09.060 に答える
3

elisp は (悲しいことに) 末尾再帰を最適化しないことに注意してください。そのため、この関数を記述するには非常に非効率的な方法であることがわかります。

于 2012-06-14T04:27:10.423 に答える
1

Pascal Bourguignonがすでに述べたように、文字列をリストに強制したり戻したりせずに文字列を使用する方が良いアプローチです。以下は私の見解です。リテラル文字列の句読点が検証される点が少し異なります。また、次の文字が大文字になるなどの句読点があるように見える場合は、大文字にもなります。これが不利な点かどうかはわかりません。そのため、この違いに気を配りませんでした。

(defun tweak-text (source)
  (let ((i 0) (separator "") (cap t) current)
    (with-output-to-string
      (dolist (i source)
        (setq current
              (concat separator 
                      (etypecase i
                        (string i)
                        (symbol (downcase (symbol-name i)))))
              separator " ")
        (let (current-char)
          (dotimes (j (length current))
            (setq current-char (aref current j))
            (cond
             ((position current-char " \t\n\r"))
             (cap (setq cap nil
                        current-char (upcase current-char)))
             ((position current-char ".?!")
              (setq cap t)))
            (princ (char-to-string current-char))))))))

(tweak-text '(not only does this sentence have a "comma," it also mentions the "iPad."))
"Not only does this sentence have a comma, it also mentions the iPad."
于 2012-06-16T08:37:52.003 に答える
1

tweak-text で再帰すると、実際に「max-lisp-eval-depth」の制限に達しています。コードのやり方に問題はありません(意図したとおりに動作しているかどうかは確認していません)。

「max-lisp-eval-depth」制限を設定/引き上げることができます。その変数のドキュメントには、スタック スペースが不足しないという確信がある限り、変数を増やすことができると記載されています。私のマシンでは、制限は保守的に 541 に設定されています。それを600に上げると、上記の関数定義が、例として与えた入力で機能します。

于 2012-06-14T03:30:54.243 に答える
0

次のように書くべきだと思います。

(defun tweak-text-wrapper (&rest args)
  (let ((max-lisp-eval-depth 9001)) ; as much as you want
    (apply tweak-text args)))
于 2012-06-14T04:34:42.367 に答える