Skip to content

LISP - 获取子字符串

在 Common Lisp 中,字符串是专门的一维字符数组。由于字符串是序列,因此许多强大的序列函数可用于操作它们。提取字符串一部分最基本的操作是创建子字符串。

subseq 函数(sub-sequence 的缩写,即“子序列”)是提取任何序列(包括字符串)一部分的主要工具。其语法是 (subseq sequence start &optional end)。

  • sequence:要从中提取的字符串(或其他序列)。
  • start:子字符串的基于 0 的起始索引(包含)。
  • end:可选的基于 0 的结束索引(不包含)。如果省略,子字符串将延伸到字符串的末尾。
(defparameter *message* "Welcome to the Lisp world!")
;; 获取从索引 15 到末尾的子字符串
(format t "Substring (end): '~a'~%" (subseq *message* 15))
;; 获取从索引 8 到 14 的子字符串
(format t "Substring (range): '~a'~%" (subseq *message* 8 14))
Substring (end): 'Lisp world!'
Substring (range): 'to the'

使用无效索引调用 subseq 会引发错误。验证索引或优雅地处理潜在错误是良好的实践,例如,通过使用 handler-case。

(defun safe-subseq (str start &optional end)
"一个安全的 subseq 包装器,在出错时返回 NIL。"
(handler-case (subseq str start end)
;; 在 Common Lisp 中,所有错误都继承自 `error` 条件。
(error (c)
(format t "Error while getting substring: ~a~%" c)
nil)))
;; --- 用法 ---
;; 这将失败,因为起始索引超出范围
(let ((result (safe-subseq "Hello" 10)))
(format t "Result of invalid call: ~a~%" result))
;; 这将失败,因为结束索引超出范围
(let ((result (safe-subseq "Hello" 0 100)))
(format t "Result of invalid call: ~a~%" result))
Error while getting substring: The start bounding index, 10, is beyond the end of the sequence, 5.
Result of invalid call: NIL
Error while getting substring: The end bounding index, 100, is beyond the end of the sequence, 5.
Result of invalid call: NIL

handler-case 宏尝试执行其第一个表单。如果在执行期间引发 error 条件,它会捕获条件对象 c,打印一条消息,并返回 nil 而不是使程序崩溃。

子字符串对于解析结构化文本至关重要。让我们编写一个函数来将类似 "[INFO] 2023-10-27: User logged in." 的日志条目解析成其组成部分。

(defun parse-log-entry (log-line)
"解析日志条目并将其组件作为列表返回。"
(let* ((level-end (position #] log-line))
(date-start (+ level-end 2))
(date-end (+ date-start 10))
(msg-start (+ date-end 2)))
(list
:level (subseq log-line 1 level-end)
:date (subseq log-line date-start date-end)
:message (subseq log-line msg-start))))
;; --- 用法 ---
(defparameter *log1* "[INFO] 2023-10-27: User logged in.")
(defparameter *log2* "[ERROR] 2023-10-28: Database connection failed.")
(print (parse-log-entry *log1*))
(print (parse-log-entry *log2*))
(:LEVEL "INFO" :DATE "2023-10-27" :MESSAGE "User logged in.")
(:LEVEL "ERROR" :DATE "2023-10-28" :MESSAGE "Database connection failed.")

除了 subseq 之外,Lisp 标准库还包括许多其他有用的字符串操作函数:

  • 搜索:(search "needle" "haystack") 查找子字符串的起始位置。
  • 拼接:(concatenate 'string "Hello, " "world!") 将多个字符串连接在一起。
  • 修剪:(string-trim '(#\Space #\Tab) " some text ") 从字符串两端移除空白字符。
  • 大小写转换:string-upcase、string-downcase 和 string-capitalize 用于改变字符串的大小写。