递归函数遍历字符串以找到给定左括号的右括号



我试图制作一个返回给定左括号的右括号的函数,我已经可以做到了,但在我的第一次尝试中,我不明白我的错误在哪里,我想知道为了避免将来出现同样的问题,这是我的第二个函数,它工作得很好(看到它工作得比我第一次尝试的更轻(:

(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

主答案

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

然而,正确的方法是从next-start-bracket跳到next-(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

Namings

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

(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))))

最新更新