0

我正在尝试创建一个返回给定左括号的右括号的函数,我已经可以做到这一点,但是在我的第一次尝试中,我不明白我的错误在哪里,我想知道在未来,这是我的第二个功能,它运行良好(看到它可以让我在第一次尝试中尝试的内容更轻松):

(defun properties-without-spaces (s)
  (let ((char-list nil)
        (char-remove-p nil))
    (loop for c across s do
      (when (or (char-equal c #\:)
                (char-equal c #\;))
        (push c char-list)
        (setf char-remove-p t))
      (when (and char-remove-p
                 (not (or (char-equal c #\:)
                          (char-equal c #\;)))
                 (not (or (char-equal c #\Space)
                          (char-equal c #\Newline)
                          (char-equal c #\Tab))))
        (setf char-remove-p nil))
      (unless char-remove-p
        (push c char-list)))
    (trim (make-string-from-chars (reverse char-list)))))

(equal (end-bracket-index "div,ul,li:focus {outline:none; margin: 5px 0 0 1px;} body {background-color: blue;}" 16)
       51) ;; => T

(equal (end-bracket-index "div,ul,li:focus {outline:none; margin: 5px 0 0 1px;} body {background-color: blue;} @media (min-width: 30em) {div {margin: 3px 0 1px 3px;} body {background-color: blue;}}" 109)
       169) ;; => T

(equal (end-bracket-index "div,ul,li:focus {/*By Umaui*/
    outline:none; /*something*/
    margin: 5px 0 0 1px;
    
    }
    
    body {
        background-color: pink;
    }
    
    @media (min-width: 30em) {
      div {
         margin: 3px 0 1px 3px;
      }
      body {
        background-color: blue;
      }
    }" 154)
       236) ;; => T

我知道有其他方法可以做到这一点,我可以使用 cl-ppcre,但这不是我的问题,我的问题是我在下面的递归函数中的错误在哪里:

(defun end-bracket-index (css start-bracket &optional (open-bracket-level 1))
  (let* ((css-before-start-bracket (subseq css 0 (+ start-bracket 1)))
         (css-after-start-bracket (subseq css (+ start-bracket 1)))
         (next-start-bracket
          (search "{" css-after-start-bracket))
         (next-end-bracket
          (search "}" css-after-start-bracket)))

    (cond ((and next-start-bracket
                next-end-bracket)
           (if (< next-start-bracket next-end-bracket)
               (incf open-bracket-level)
               (decf open-bracket-level)))
          ((and (null next-start-bracket)
                next-end-bracket)
           (decf open-bracket-level)))
    
    (when (zerop open-bracket-level)
      (return-from end-bracket-index
        (+ next-end-bracket
           (length css-before-start-bracket))))
    
    (end-bracket-index css
                       (+ next-start-bracket
                          (length css-before-start-bracket))
                       open-bracket-level)))
    
(equal (end-bracket-index "div,ul,li:focus {outline:none; margin: 5px 0 0 1px;} body {background-color: blue;}" 16)
       51) ;; => T

(equal (end-bracket-index "div,ul,li:focus {outline:none; margin: 5px 0 0 1px;} body {background-color: blue;} @media (min-width: 30em) {div {margin: 3px 0 1px 3px;} body {background-color: blue;}}" 109)
       169) ;; => NIL because 168 not equal 169

(equal (end-bracket-index "div,ul,li:focus {/*By Umaui*/
    outline:none; /*something*/
    margin: 5px 0 0 1px;

    }

    body {
        background-color: pink;
    }

    @media (min-width: 30em) {
      div {
         margin: 3px 0 1px 3px;
      }
      body {
        background-color: blue;
      }
    }" 154)
       236) ;; NIL because because 234 not equal 236
4

1 回答 1

1

主要答案

你不工作的尝试从一个开始跳到下一个开始,但如果没有开始跟随,它会保持在相同的位置,而open-bracket-level正在下降。

但是,正确的方法是跳转next-start-bracketnext-(start|end)-bracket. (因为有时只有next-end-breackets 会跟随而没有next-start-brackets 出现在最后)。因此,必须更正最后的递归调用以确定是跳转到next-start-bracket还是跳转到next-end-bracket.

(defun end-bracket-index (css start-bracket &optional (open-bracket-level 1))
  (let* ((css-before-start-bracket (subseq css 0 (1+ start-bracket)))
         (css-after-start-bracket (subseq css (1+ start-bracket)))
         (next-start-bracket (search "{" css-after-start-bracket))
         (next-end-bracket (search "}" css-after-start-bracket)))

    (cond ((and next-start-bracket next-end-bracket) (if (< next-start-bracket next-end-bracket)
                                                         (incf open-bracket-level)
                                                         (decf open-bracket-level)))
          ((and (null next-start-bracket) next-end-bracket) (decf open-bracket-level)))

    (when (zerop open-bracket-level)
      (return-from end-bracket-index (+ next-end-bracket (length css-before-start-bracket))))

    (end-bracket-index css
                       (+ (if (and next-start-bracket next-end-bracket)
                              (min next-start-bracket next-end-bracket)
                              next-end-bracket)
                          (length css-before-start-bracket))
                       open-bracket-level)))

调试

在退出when条件之前添加一个打印命令帮助我找到了这一切:

(format t "pos: ~A~%next-start: ~A ~%next-stop: ~A~%level: ~A~%" start-bracket next-start-bracket next-end-bracket open-bracket-level)

测试

你的测试是错误的clisp。所以我纠正了测试:

(defparameter *ex* (string "div,ul,li:focus {/*By Umaui*/
    outline:none; /*something*/
    margin: 5px 0 0 1px;

    }

    body {
        background-color: pink;
    }

    @media (min-width: 30em) {
      div {
         margin: 3px 0 1px 3px;
      }
      body {
        background-color: blue;
      }
    }"))

(eql T (every #'identity (list (equal (end-bracket-index *ex* 16) 92)
                               (equal (end-bracket-index *ex* 104) 142)
                               (equal (end-bracket-index *ex* 174) 285)
                               (equal (end-bracket-index *ex* 239) 279))))
;; gives T

命名

由于跳跃/步进不会从一个开始到下一个开始括号发生,我建议使用不同的命名:

(defun end-bracket-index (str pos &optional (level 1))
  (let* ((str-pre (subseq str 0 (1+ pos)))
         (str-post (subseq str (1+ pos)))
         (next-open (search "{" str-post))
         (next-close (search "}" str-post)))

    (cond ((and next-open next-close) (if (< next-open next-close)
                                          (incf level)
                                          (decf level)))
          ((and (null next-open) next-close) (decf level)))

    (when (zerop level)
      (return-from end-bracket-index (+ next-close (length str-pre))))

    (end-bracket-index str
                       (+ (if (and next-open next-close)
                              (min next-open next-close)
                              next-close)
                          (length str-pre))
                       level)))

另一种可能

(defun chop-to-next-bracket (s)
  "Returns 3 values: 
   first value: string s shortened by piece until next bracket 
                (excluding the next bracket -> given as third value)
   second value: how many positions to add to counter `pos`
   third value: which next bracket `{` or `}` or empty string."
  (let ((next-open (search "{" s))
        (next-close  (search "}" s)))
    (cond ((and next-open next-close) (if (< next-open next-close)
                                          (values (subseq s (1+ next-open)) (1+ next-open) "{")
                                          (values (subseq s (1+ next-close)) (1+ next-close) "}")))
          ((null next-open) (values (subseq s (1+ next-close)) (1+ next-close) "}"))
          ((null next-close) (values (subseq s (1+ next-open)) (1+ next-open) "{"))
          (t (values nil (length s) "")))))

(defun end-bracket-index (str pos)
    (labels ((walker (str pos level)
               (multiple-value-bind (str-new counter bracket) (chop-to-next-bracket str)
                 (cond ((null str-new) nil)
                       ((string= bracket "{") (walker str-new (+ pos counter) (1+ level)))
                       ((string= bracket "}") (if (zerop level)
                                                  (+ pos counter)
                                                  (walker str-new (+ pos counter) (1- level))))
                       (t nil)))))
      (incf pos (search "{" (subseq str pos))) ;; ensure pos is pos of very next "{"
      (walker (subseq str (1+ pos)) pos 0)))

更高效的解决方案

逐个字符地扫描字符更容易,效率更高。

(defun end-bracket-index (css start-pos)
  (labels ((scanner (sl &optional (level 0) (pos 0))
             (cond ((null sl) nil)
                   ((eql #\{ (car sl)) (scanner (cdr sl) (1+ level) (1+ pos)))
                   ((eql #\} (car sl)) (if (zerop (1- level))
                                           pos
                                           (scanner (cdr sl) (1- level) (1+ pos))))
                   (t (scanner (cdr sl) level (1+ pos))))))
    (let ((sub (coerce (subseq css start-pos) 'list)))
      (assert (eql #\{ (car sub)))
      (scanner sub 0 start-pos))))
于 2020-08-07T11:50:26.723 回答