按定界符分割字符串并包含定界符-Common Lisp

时间:2019-12-29 02:37:46

标签: string split common-lisp

如何用Common Lisp中的定界符分割字符串,就像在SPLIT-SEQUENCE中一样,还要在字符串列表中添加定界符?

例如,我可以写: (split-string-with-delimiter #\. "a.bc.def.com") 结果将是("a" "." "bc" "." "def" "." "com")

我尝试了以下代码(make-adjustable-string生成了可以用vector-push-extend扩展的字符串):

(defun make-adjustable-string (s)
  (make-array (length s)
    :fill-pointer (length s)
    :adjustable t
    :initial-contents s
    :element-type (array-element-type s)))

(defun split-str (string &key (delimiter #\ ) (keep-delimiters nil))
  "Splits a string into a list of strings, with the delimiter still
  in the resulting list."
  (let ((words nil)
        (current-word (make-adjustable-string "")))
    (do* ((i 0 (+ i 1))
          (x (char string i) (char string i)))
         ((= (+ i 1) (length string)) nil)
      (if (eql delimiter x)
        (unless (string= "" current-word)
          (push current-word words)
          (push (string delimiter) words)
          (setf current-word (make-adjustable-string "")))
        (vector-push-extend x current-word)))
    (nreverse words)))

但这不会打印出最后一个子字符串/单词。我不确定发生了什么。

感谢您的帮助!

4 个答案:

答案 0 :(得分:6)

如果您只是在寻找解决方案,而不是在练习,则可以使用cl-ppcre

CL-USER> (cl-ppcre:split "(\\.)" "a.bc.def.com" :with-registers-p t)
("a" "." "bc" "." "def" "." "com")

答案 1 :(得分:5)

像这样吗?

  • 使用subseq
  • 复制子字符串
  • 使用LOOP使收集东西更容易

示例:

(defun split-string-with-delimiter (string
                                    &key (delimiter #\ )
                                         (keep-delimiters nil)
                                    &aux (l (length string)))
  (loop for start = 0 then (1+ pos)
        for pos   = (position delimiter string :start start)

        ; no more delimiter found
        when (and (null pos) (not (= start l)))
        collect (subseq string start)

        ; while delimiter found
        while pos

        ;  some content found
        when (> pos start) collect (subseq string start pos)
        ;  optionally keep delimiter
        when keep-delimiters collect (string delimiter)))

示例:

CL-USER 120 > (split-string-with-delimiter "..1.2.3.4.."
                :delimiter #\. :keep-delimiters nil)
("1" "2" "3" "4")

CL-USER 121 > (split-string-with-delimiter "..1.2.3.4.."
                :delimiter #\. :keep-delimiters t)
("." "." "1" "." "2" "." "3" "." "4" "." ".")

CL-USER 122 > (split-string-with-delimiter "1.2.3.4"
                :delimiter #\. :keep-delimiters nil)
("1" "2" "3" "4")

CL-USER 123 > (split-string-with-delimiter "1.2.3.4"
                :delimiter #\. :keep-delimiters t)
("1" "." "2" "." "3" "." "4")

或已修改以与任何序列(列表,向量,字符串等)一起使用:

(defun split-sequence-with-delimiter (sequence delimiter
                                      &key (keep-delimiters nil)
                                      &aux (end (length sequence)))
  (loop for start = 0 then (1+ pos)
        for pos   = (position delimiter sequence :start start)

        ; no more delimiter found
        when (and (null pos) (not (= start end)))
        collect (subseq sequence start)

        ; while delimiter found
        while pos

        ;  some content found
        when (> pos start) collect (subseq sequence start pos)
        ;  optionally keep delimiter
        when keep-delimiters collect (subseq sequence pos (1+ pos))))

答案 2 :(得分:2)

问题出在do *循环的结束条件之后。当变量i到达字符串的末尾时,将退出do *循环,但仍然有一个当前单词尚未添加到单词中。满足结束条件后,您需要在退出循环之前将x添加到当前单词,然后将当前单词添加到单词:

(defun split-string-with-delimiter (string delimiter)
  "Splits a string into a list of strings, with the delimiter still
  in the resulting list."
  (let ((words nil)
        (current-word (make-adjustable-string "")))
    (do* ((i 0 (+ i 1))
          (x (char string i) (char string i)))
         ((>= (+ i 1) (length string)) (progn (vector-push-extend x current-word) (push current-word words)))
      (if (eql delimiter x)
        (unless (string= "" current-word)
          (push current-word words)
          (push (string delimiter) words)
          (setf current-word (make-adjustable-string "")))
        (vector-push-extend x current-word)))
    (nreverse words)))

但是,请注意,此版本仍然存在问题,因为如果字符串的最后一个字符是定界符,则该字符将包含在最后一个单词中,即(split-string-with-delimiter "a.bc.def." #\.) => ("a" "." "bc" "." "def.") 我让你加这张支票。

无论如何,您可能需要提高效率,方法是先查找定界符,然后一次提取单个字符串中当前i与下一个定界符之间的所有字符。

答案 3 :(得分:2)

对于要分割许多分隔符并保留它们的情况:

(defun split-string-with-delims (str delims)
  (labels ((delim-p (c)
             (position c delims))
           (tokens (stri test)
             (when (> (length stri) 0)
               (let ((p (position-if test stri)))
                 (if p
                     (if (= p 0)
                         (cons (subseq stri 0 (1+ p))
                               (tokens (subseq stri (1+ p) nil) test))
                         (cons (subseq stri 0 p)
                               (tokens (subseq stri p nil) test)))
                     (cons stri nil))))))
    (tokens str #'delim-p)))

您可以调用它:

(split-string-with-delims ".,hello world,," '(#\. #\, #\ ))
  ; => ("." "," "hello" " " "world" "," ",")

或:

(split-string-with-delims ".,hello world,,!!" "., ")
  ; => ("." "," "hello" " " "world" "," "," "!!")

关于您的代码,因为有subseq,所以我会选择Rainer Joswig的方法(上),而不是您的make-adjustable-string + vector-push-extend