值得一提的是,在 Common Lisp中,字符串是数组,即字符串也是序列。也就是说,数组和序列中的一些概念也可以应用在字符串上。当你找不到某个特定的字符串的函数时,试试在数组或序列通用的函数中找找看。

ASDF3中的一些操作字符串的函数(strcat, string-prefix-p, string-enclosed-p, first-char, last-char, split-string, stripln)。

Quicklisp 中一些拓展包有更多的函数和更简单的方法:

  • strtrim, words, unwords, lines, unlines, concat, split, shorten, repeat, replace-all, starts-with?, ends-with?, blankp, emptyp
  • cl-change-case:包括将字符串转换成驼峰命名格式、参数格式、蛇形命名格式的函数,这些函数在 str 包中也有。
  • mk-string-metrics:包含一些高效测量字符串编辑距离的函数(Damerau-Levenshtein, Hamming, Jaro, Jaro-Winkler, Levenshtein 等)
  • cl-ppcre: 常用的包,包括字符串的一些正则表达式的操作。

一些关于字符串格式的资源:

创建字符串

  • format nil
  1. (defparameter *person* "you")
  2. (format nil "hello ~a" *person*) ;; => "hello you"
  • make-string count
  1. (make-string 3 :initial-element #\♥) ;; => "♥♥♥"

访问子串:subseq

因为字符串也是序列,所以可以使用访问子序列的函数来访问子串。其中字符串的索引也和数组一样,从 0 开始。

  1. (defparameter *my-string* (string "Groucho Marx"))
  2. *MY-STRING*
  3. (subseq *my-string* 8)
  4. "Marx"
  5. (subseq *my-string* 0 7)
  6. "Groucho"
  7. (subseq *my-string* 1 5)
  8. "rouc"

也可以同时使用 subseqsetf

  1. (defparameter *my-string* (string "Harpo Marx"))
  2. *MY-STRING*
  3. (subseq *my-string* 0 5)
  4. "Harpo"
  5. (setf (subseq *my-string* 0 5) "Chico")
  6. "Chico"
  7. *my-string*
  8. "Chico Marx"

要注意的是,字符串不是可变的。引用 HyperSpec 的一段话:”如果新的字符串与子串的长度不同,由较短的字符串决定替换的长度。”

  1. (defparameter *my-string* (string "Karl Marx"))
  2. *MY-STRING*
  3. (subseq *my-string* 0 4)
  4. "Karl"
  5. (setf (subseq *my-string* 0 4) "Harpo")
  6. "Harpo"
  7. *my-string*
  8. "Harp Marx"
  9. (subseq *my-string* 4) "o Marx")
  10. "o Marx"
  11. *my-string*
  12. "Harpo Mar"

访问单个字符:char

  1. (defparameter *my-string* (string "Groucho Marx"))
  2. *MY-STRING*
  3. (char *my-string* 11)
  4. #\x
  5. (char *my-string* 7)
  6. #\Space
  7. (char *my-string* 6)
  8. #\o
  9. (setf (char *my-string* 6) #\y)
  10. #\y
  11. *my-string*
  12. "Grouchy Marx"

schar 适用于对效率要求比较高的场景。

由于字符串是数组,因此也是序列,所以也可以经常使用 arefelt 函数( char 的效率可能要高一些。)

  1. (defparameter *my-string* (string "Groucho Marx"))
  2. *MY-STRING*
  3. (aref *my-string* 3)
  4. #\u
  5. (elt *my-string* 8)
  6. #\M
  1. (stream-external-format *standard-output*)
  2. :UTF-8
  3. (code-char 200)
  4. #\LATIN_CAPITAL_LETTER_E_WITH_GRAVE
  5. (char-code #\LATIN_CAPITAL_LETTER_E_WITH_GRAVE)
  6. 200
  7. (code-char 1488)
  8. #\HEBREW_LETTER_ALEF
  9. (char-code #\HEBREW_LETTER_ALEF)
  10. 1488

操作字符串的部分

  1. * (remove #\o "Harpo Marx")
  2. "Harp Marx"
  3. * (remove #\a "Harpo Marx")
  4. "Hrpo Mrx"
  5. * (remove #\a "Harpo Marx" :start 2)
  6. "Harpo Mrx"
  7. * (remove-if #'upper-case-p "Harpo Marx")
  8. "arpo arx"
  9. * (substitute #\u #\o "Groucho Marx")
  10. "Gruuchu Marx"
  11. * (substitute-if #\_ #'upper-case-p "Groucho Marx")
  12. "_roucho _arx"
  13. * (defparameter *my-string* (string "Zeppo Marx"))
  14. *MY-STRING*
  15. * (replace *my-string* "Harpo" :end1 5)
  16. "Harpo Marx"
  17. * *my-string*
  18. "Harpo Marx"
  1. * (replace-all "Groucho Marx Groucho" "Groucho" "ReplacementForGroucho")
  2. "ReplacementForGroucho Marx ReplacementForGroucho"
  1. (defun replace-all (string part replacement &key (test #'char=))
  2. "Returns a new string in which all the occurrences of the part
  3. is replaced with replacement."
  4. (with-output-to-string (out)
  5. (loop with part-length = (length part)
  6. for old-pos = 0 then (+ pos part-length)
  7. for pos = (search part string
  8. :start2 old-pos
  9. :test test)
  10. do (write-string string out
  11. :start old-pos
  12. :end (or pos (length string)))
  13. when pos do (write-string replacement out)
  14. while pos)))

追加字符串

concatenate

  1. * (concatenate 'string "Karl" " " "Marx")
  2. "Karl Marx"
  3. * (concatenate 'list "Karl" " " "Marx")
  4. (#\K #\a #\r #\l #\Space #\M #\a #\r #\x)

strcat concat

  1. (uiop:strcat "karl" " marx")
  2. (str:concat "foo" "bar")

构建由多个部分组成的字符串

  1. * (defparameter *my-string* (make-array 0
  2. :element-type 'character
  3. :fill-pointer 0
  4. :adjustable t))
  5. *MY-STRING*
  6. * *my-string*
  7. ""
  8. * (dolist (char '(#\Z #\a #\p #\p #\a))
  9. (vector-push-extend char *my-string*))
  10. NIL
  11. * *my-string*
  12. "Zappa"

format

  1. * (format nil "This is a string with a list ~A in it"
  2. '(1 2 3))
  3. "This is a string with a list (1 2 3) in it"
  4. * (format nil "The Marx brothers are:~{ ~A~^,~}."
  5. '("Groucho" "Harpo" "Chico" "Zeppo" "Karl"))
  6. "The Marx brothers are: Groucho, Harpo, Chico, Zeppo, Karl."

concatenate

  1. * (format nil "The Marx brothers are:~{ ~A~}."
  2. '("Groucho" "Harpo" "Chico" "Zeppo" "Karl"))
  3. "The Marx brothers are: Groucho Harpo Chico Zeppo Karl."

with-output-to-string

  1. * (with-output-to-string (stream)
  2. (dolist (char '(#\Z #\a #\p #\p #\a #\, #\Space))
  3. (princ char stream))
  4. (format stream "~S - ~S" 1940 1993))
  5. "Zappa, 1940 - 1993"

同一时间处理字符串的一个字符

map

  1. * (defparameter *my-string* (string "Groucho Marx"))
  2. *MY-STRING*
  3. * (map 'string #'(lambda (c) (print c)) *my-string*)
  4. #\G
  5. #\r
  6. #\o
  7. #\u
  8. #\c
  9. #\h
  10. #\o
  11. #\Space
  12. #\M
  13. #\a
  14. #\r
  15. #\x
  16. "Groucho Marx"

loop

  1. * (loop for char across "Zeppo"
  2. collect char)
  3. (#\Z #\e #\p #\p #\o)

字符串倒序

reversee

  1. (defparameter *my-string* (string "DSL"))
  2. *MY-STRING*
  3. (reverse *my-string*)
  4. "LSD"

Common Lisp 中没有直接将字符串按照单词进行倒序的,你需要自己去定义函数或是使用 str

  1. (defparameter *singing* "singing in the rain")
  2. *SINGING*
  3. (str:words *SINGING*)
  4. ("singing" "in" "the" "rain")
  5. (reverse *)
  6. ("rain" "the" "in" "singing")
  7. (str:unwords *)
  8. "rain the in singing"

自定义函数

  1. * (defun split-by-one-space (string)
  2. "Returns a list of substrings of string
  3. divided by ONE space each.
  4. Note: Two consecutive spaces will be seen as
  5. if there were an empty string between them."
  6. (loop for i = 0 then (1+ j)
  7. as j = (position #\Space string :start i)
  8. collect (subseq string i j)
  9. while j))
  10. SPLIT-BY-ONE-SPACE
  11. * (split-by-one-space "Singing in the rain")
  12. ("Singing" "in" "the" "rain")
  13. * (split-by-one-space "Singing in the rain")
  14. ("Singing" "in" "the" "" "rain")
  15. * (split-by-one-space "Cool")
  16. ("Cool")
  17. * (split-by-one-space " Cool ")
  18. ("" "Cool" "")
  19. * (defun join-string-list (string-list)
  20. "Concatenates a list of strings
  21. and puts spaces between the elements."
  22. (format nil "~{~A~^ ~}" string-list))
  23. JOIN-STRING-LIST
  24. * (join-string-list '("We" "want" "better" "examples"))
  25. "We want better examples"
  26. * (join-string-list '("Really"))
  27. "Really"
  28. * (join-string-list '())
  29. ""
  30. * (join-string-list
  31. (nreverse
  32. (split-by-one-space
  33. "Reverse this sentence by word")))
  34. "word by sentence this Reverse"

大小写转换

  1. * (string-upcase "cool")
  2. "COOL"
  3. * (string-upcase "Cool")
  4. "COOL"
  5. * (string-downcase "COOL")
  6. "cool"
  7. * (string-downcase "Cool")
  8. "cool"
  9. * (string-capitalize "cool")
  10. "Cool"
  11. * (string-capitalize "cool example")
  12. "Cool Example"
  13. * (string-capitalize "cool example" :start 5)
  14. "cool Example"
  15. * (string-capitalize "cool example" :end 5)
  16. "Cool example"
  17. * (defparameter *my-string* (string "BIG"))
  18. *MY-STRING*
  19. * (defparameter *my-downcase-string* (nstring-downcase *my-string*))
  20. *MY-DOWNCASE-STRING*
  21. * *my-downcase-string*
  22. "big"
  23. * *my-string*
  24. "big"

在 HyperSpec 中有一段话

for STRING-UPCASE, STRING-DOWNCASE, and STRING-CAPITALIZE, string is not modified. However, if no characters in string require conversion, the result may be either string or a copy of it, at the implementation’s discretion.

也就是说,下面这个例子中与实现无关,结果可能是 “BIG” 或是 “BUG”。如果想要一个确切的结果,使用 copy-seq

  1. (defparameter *my-string* (string "BIG"))
  2. *MY-STRING*
  3. (defparameter *my-upcase-string* (string-upcase *my-string*))
  4. *MY-UPCASE-STRING*
  5. (setf (char *my-string* 1) #\U)
  6. #\U
  7. *my-string*
  8. "BUG"
  9. *my-upcase-string*
  10. "BIG"

使用 format 函数

转换为小写:~( ~)

  1. (format t "~(~a~)" "HELLO WORLD")
  2. ;; => hello world

首字母大写:~:( ~)

  1. (format t "~:(~a~)" "HELLO WORLD")
  2. Hello World
  3. NIL

第一个单词首字母大写:~@( ~)

  1. (format t "~@(~a~)" "hello world")
  2. Hello world
  3. NIL

转换为大写:~@:( ~)

  1. (format t "~@:(~a~)" "hello world")
  2. HELLO WORLD
  3. NIL

删除空格

string-trim

  1. (string-trim " " " trim me ")
  2. "trim me"
  3. (string-trim " et" " trim me ")
  4. "rim m"
  5. (string-left-trim " et" " trim me ")
  6. "rim me "
  7. (string-right-trim " et" " trim me ")
  8. " trim m"
  9. (string-right-trim '(#\Space #\e #\t) " trim me ")
  10. " trim m"
  11. (string-right-trim '(#\Space #\e #\t #\m) " trim me ")

符号与字符串之间的转换

intern

  1. * (in-package "COMMON-LISP-USER")
  2. #<The COMMON-LISP-USER package, 35/44 internal, 0/9 external>
  3. * (intern "MY-SYMBOL")
  4. MY-SYMBOL
  5. NIL
  6. * (intern "MY-SYMBOL")
  7. MY-SYMBOL
  8. :INTERNAL
  9. * (export 'MY-SYMBOL)
  10. T
  11. * (intern "MY-SYMBOL")
  12. MY-SYMBOL
  13. :EXTERNAL
  14. * (intern "My-Symbol")
  15. |My-Symbol|
  16. NIL
  17. * (intern "MY-SYMBOL" "KEYWORD")
  18. :MY-SYMBOL
  19. NIL
  20. * (intern "MY-SYMBOL" "KEYWORD")
  21. :MY-SYMBOL
  22. :EXTERNAL
  1. * (symbol-name 'MY-SYMBOL)
  2. "MY-SYMBOL"
  3. * (symbol-name 'my-symbol)
  4. "MY-SYMBOL"
  5. * (symbol-name '|my-symbol|)
  6. "my-symbol"
  7. * (string 'howdy)
  8. "HOWDY"

字符与字符串之间的转换

  1. * (coerce "a" 'character)
  2. #\a
  3. * (coerce (subseq "cool" 2 3) 'character)
  4. #\o
  5. * (coerce "cool" 'list)
  6. (#\c #\o #\o #\l)
  7. * (coerce '(#\h #\e #\y) 'string)
  8. "hey"
  9. * (coerce (nth 2 '(#\h #\e #\y)) 'character)
  10. #\y
  11. * (defparameter *my-array* (make-array 5 :initial-element #\x))
  12. *MY-ARRAY*
  13. * *my-array*
  14. #(#\x #\x #\x #\x #\x)
  15. * (coerce *my-array* 'string)
  16. "xxxxx"
  17. * (string 'howdy)
  18. "HOWDY"
  19. * (string #\y)
  20. "y"
  21. * (coerce #\y 'string)
  22. #\y can't be converted to type STRING.
  23. [Condition of type SIMPLE-TYPE-ERROR]

字符串查找

find, position

  1. * (find #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equal)
  2. #\t
  3. * (find #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equalp)
  4. #\T
  5. * (find #\z "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equalp)
  6. NIL
  7. * (find-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks.")
  8. #\1
  9. * (find-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks." :from-end t)
  10. #\0
  11. * (position #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equal)
  12. 17
  13. * (position #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equalp)
  14. 0
  15. * (position-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks.")
  16. 37
  17. * (position-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks." :from-end t)
  18. 43

count

  1. * (count #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equal)
  2. 2
  3. * (count #\t "The Hyperspec contains approximately 110,000 hyperlinks." :test #'equalp)
  4. 3
  5. * (count-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks.")
  6. 6
  7. * (count-if #'digit-char-p "The Hyperspec contains approximately 110,000 hyperlinks." :start 38)
  8. 5

查找子串: search

  1. * (search "we" "If we can't be free we can at least be cheap")
  2. 3
  3. * (search "we" "If we can't be free we can at least be cheap" :from-end t)
  4. 20
  5. * (search "we" "If we can't be free we can at least be cheap" :start2 4)
  6. 20
  7. * (search "we" "If we can't be free we can at least be cheap" :end2 5 :from-end t)
  8. 3
  9. * (search "FREE" "If we can't be free we can at least be cheap")
  10. NIL
  11. * (search "FREE" "If we can't be free we can at least be cheap" :test #'char-equal)
  12. 15

将字符串转换为数字

转换为整数型:parse-integer

  1. * (parse-integer "42")
  2. 42
  3. 2
  4. * (parse-integer "42" :start 1)
  5. 2
  6. 2
  7. * (parse-integer "42" :end 1)
  8. 4
  9. 1
  10. * (parse-integer "42" :radix 8)
  11. 34
  12. 2
  13. * (parse-integer " 42 ")
  14. 42
  15. 3
  16. * (parse-integer " 42 is forty-two" :junk-allowed t)
  17. 42
  18. 3
  19. * (parse-integer " 42 is forty-two")
  20. Error in function PARSE-INTEGER:
  21. There's junk in this string: " 42 is forty-two".

转换成任意的数字:read-from-string

  1. * (read-from-string "#X23")
  2. 35
  3. 4
  4. * (read-from-string "4.5")
  5. 4.5
  6. 3
  7. * (read-from-string "6/8")
  8. 3/4
  9. 3
  10. * (read-from-string "#C(6/8 1)")
  11. #C(3/4 1)
  12. 9
  13. * (read-from-string "1.2e2")
  14. 120.00001
  15. 5
  16. * (read-from-string "symbol")
  17. SYMBOL
  18. 6
  19. * (defparameter *foo* 42)
  20. *FOO*
  21. * (read-from-string "#.(setq *foo* \"gotcha\")")
  22. "gotcha"
  23. 23
  24. * *foo*
  25. "gotcha"

转换成浮点型:parse-float

  1. (ql:quickload "parse-float")
  2. (parse-float:parse-float "1.2e2")
  3. ;; 120.00001
  4. ;; 5

数字转换成字符串

write-to-string prin1-to-string princ-to-string

  1. (write-to-string 250)
  2. "250"
  3. (write-to-string 250.02)
  4. "250.02"
  5. (write-to-string 250 :base 5)
  6. "2000"
  7. (write-to-string (/ 1 3))
  8. "1/3"

字符串比较

equal, equalp, mismatch

  1. (string= "Marx" "Marx")
  2. T
  3. (string= "Marx" "marx")
  4. NIL
  5. (string-equal "Marx" "marx")
  6. T
  7. (string< "Groucho" "Zeppo")
  8. T
  9. (string "groucho" "Zeppo")
  10. NIL
  11. (string-lessp "groucho" "Zeppo")
  12. 0
  13. (mismatch "Harpo Marx" "Zeppo Marx" :from-end t :test #'char=)
  14. 3

字符串格式

  1. (defparameter movies '(
  2. (1 "Matrix" 5)
  3. (10 "Matrix Trilogy swe sub" 3.3)
  4. ))

结果如下:

  1. 1 Matrix 5
  2. 10 Matrix Trilogy swe sub 3.3

使用 mapcar 格式化

  1. (mapcar (lambda (it)
  2. (format t "~a ~a ~a~%" (first it) (second it) (third it)))
  3. movies)

输出结果为:

  1. 1 foo baar 5
  2. 10 last 3.3

format 的结构

使用 format 进行格式化时,都是以 ~ 开始,要输出 ‘~’ 符号时,使用 ~~, 多个 ‘~’: ~10~

  • R: 将数字转换成英文: (format t "~R" 20) => “twenty”
  • $: 金融: (format t "~$" 21982) => 21892.00
  • D, B, O, X: 十进制,二进制,八进制,十六进制
  • F: 修复的浮点数
  • ~A(~a): (format t "~a" movies) => “((1 Matrix 5) (10 Matrix Trilogy swe sub 3.3))”
  • ~%/~&: ~& 不会输出新行如果输出的流已经有了。输出 10 个新行 ~10%
  • ~T: ~10T 10 个 tab

文本对齐

右对齐:a

  1. (format nil "~20a" "yo")
  2. ;; "yo "
  3. (mapcar (lambda (it)
  4. (format t "~2a ~a ~a~%" (first it) (second it) (third it)))
  5. movies)
  6. ;; 1 Matrix 5
  7. ;; 10 Matrix Trilogy swe sub 3.3
  8. (mapcar (lambda (it)
  9. (format t "~2a ~25a ~2a~%" (first it) (second it) (third it)))
  10. movies)
  11. ;; 1 Matrix 5
  12. ;; 10 Matrix Trilogy swe sub 3.3

左对齐: @

  1. (format nil "~20@a" "yo")
  2. ;; " yo"
  3. (mapcar (lambda (it)
  4. (format nil "~2@a ~25@a ~2a~%" (first it) (second it) (third it)))
  5. movies)
  6. ;; 1 Matrix 5
  7. ;;10 Matrix Trilogy swe sub 3.3

小数对齐:F

  1. (format t "~,2F" 20.1)
  2. ;; 20.10
  3. (mapcar (lambda (it)
  4. (format t "~2@a ~25a ~2,2f~%" (first it) (second it) (third it)))
  5. movies)
  6. ;; 1 Matrix 5.00
  7. ;; 10 Matrix Trilogy swe sub 3.30

格式化已格式的字符串

  1. (let ((padding 30))
  2. (format nil "~va" padding "foo"))
  3. ;; "foo "

Capturing what is is printed into a stream

Inside (with-output-to-string (mystream) …), everything that is
printed into the stream mystream is captured and returned as a
string:

  1. (defun greet (name &key (stream t))
  2. ;; by default, print to standard output.
  3. (format stream "hello ~a" name))
  4. (let ((output (with-output-to-string (stream)
  5. (greet "you" :stream stream))))
  6. (format t "Output is: '~a'. It is indeed a ~a, aka a string.~&" output (type-of output)))
  7. ;; Output is: 'hello you'. It is indeed a (SIMPLE-ARRAY CHARACTER (9)), aka a string.
  8. ;; NIL

See also