我想删除字符串末尾的一些字符。
我做了这个功能:
(defun del-delimiter-at-end (string)
(cond
((eq (delimiterp (char string (- (length string) 1))) nil)
string )
(t
(del-delimiterp-at-end (subseq string 0 (- (length string) 1))) ) ) )
用这个:
(defun delimiterp (c) (position c " ,.;!?/"))
但我不明白为什么它不起作用。我有以下错误:
Index must be positive and not -1
请注意,我想在字符串列表中拆分一个字符串,我已经看过了:
Lisp - Splitting Input into Separate Strings
但如果字符串的结尾是分隔符,它就不起作用,这就是我试图这样做的原因。
我做错了什么? 提前谢谢。
答案 0 :(得分:8)
只需使用string-right-trim
:
(string-right-trim " ,.;!?/" s)
如果您将空字符串传递给del-delimiter-at-end
,则会将-1
作为第二个参数传递给char
。
答案 1 :(得分:2)
这里真的有两个问题。一个更具体,并在问题正文中描述。另一个更通用,是标题所要求的(如何拆分序列)。我将处理身体中的直接问题,如何从序列的末尾修剪一些元素。然后我将处理一般性的如何拆分序列以及如何在特殊情况下拆分列表的更一般的问题,因为根据其标题找到该问题的人可能会对此感兴趣。
sds answered如果您只关心字符串,那就完美了。该语言已包含string-right-trim
,因此,如果您只关注字符串,这可能是解决此问题的最佳方法。
也就是说,如果您希望基于subseq
的方法适用于任意序列,则使用该语言提供的其他序列操作函数是有意义的。许多函数采用:from-end
参数并且具有可以提供帮助的-if-not
变体。在这种情况下,您可以使用position-if-not
查找序列中最右侧的非分隔符,然后使用subseq
:
(defun delimiterp (c)
(position c " ,.;!?/"))
(defun right-trim-if (sequence test)
(let ((pos (position-if-not test sequence :from-end t)))
(subseq sequence 0 (if (null pos) 0 (1+ pos)))))
(right-trim-if "hello!" 'delimiterp) ; some delimiters to trim
;=> "hello"
(right-trim-if "hi_there" 'delimiterp) ; nothing to trim, with other stuff
;=> "hi_there"
(right-trim-if "?" 'delimiterp) ; only delimiters
;=> ""
(right-trim-if "" 'delimiterp) ; nothing at all
;=> ""
complement
和position
有些人可能会指出position-if-not
已被弃用。如果您不想使用它,可以使用complement
和position-if
来达到同样的效果。 (我没有注意到对-if-not
函数的实际厌恶。)complement
上的HyperSpec条目说:
在Common Lisp中,名称与
中使用名称为xxx-if-not
相关的函数是相关的 在xxx-if
的函数(xxx-if-not f . arguments) == (xxx-if (complement f) . arguments)
例如,
(find-if-not #'zerop '(0 0 3)) == (find-if (complement #'zerop) '(0 0 3)) => 3
请注意,由于
xxx-if-not
功能和:test-not
参数已被弃用,使用xxx-if
函数或:test
补充参数是首选。
也就是说,position
和position-if-not
采用函数指示符,这意味着您可以将符号 delimiterp
传递给它们,就像我们在
(right-trim-if "hello!" 'delimiterp) ; some delimiters to trim
;=> "hello"
但是, complement
不需要函数指示符(即符号或函数),它实际上需要一个函数对象。因此,您可以将right-trim-if
定义为
(defun right-trim-if (sequence test)
(let ((pos (position-if (complement test) sequence :from-end t)))
(subseq sequence 0 (if (null pos) 0 (1+ pos)))))
但你必须用函数对象来调用它,而不是符号:
(right-trim-if "hello!" #'delimiterp)
;=> "hello"
(right-trim-if "hello!" 'delimiterp)
; Error
如果你不只是试图修正序列,那么你可以毫不费力地实现分割功能。想法是将“开始”指针递增到序列中。它首先指向序列的开头。然后你找到第一个分隔符并抓住它们之间的子序列。然后在此之后找到下一个非分隔符,并将其视为新的起点。
(defun split (sequence test)
(do ((start 0)
(results '()))
((null start) (nreverse results))
(let ((p (position-if test sequence :start start)))
(push (subseq sequence start p) results)
(setf start (if (null p)
nil
(position-if-not test sequence :start p))))))
这适用于多种序列,并且在子序列中最终不会出现非分隔符:
CL-USER> (split '(1 2 4 5 7) 'evenp)
((1) (5 7))
CL-USER> (split '(1 2 4 5 7) 'oddp)
(NIL (2 4))
CL-USER> (split "abc123def456" 'alpha-char-p)
("" "123" "456")
CL-USER> (split #(1 2 3 foo 4 5 6 let 7 8 list) 'symbolp)
(#(1 2 3) #(4 5 6) #(7 8))
虽然这适用于所有类型的序列,但它对列表效率不高,因为subseq
,position
等都必须遍历列表到start
位置。对于列表,最好使用列表特定的实现:
(defun split-list (list test)
(do ((results '()))
((endp list)
(nreverse results))
(let* ((tail (member-if test list))
(head (ldiff list tail)))
(push head results)
(setf list (member-if-not test tail)))))
CL-USER> (split-list '(1 2 4 5 7) 'oddp)
(NIL (2 4))
CL-USER> (split-list '(1 2 4 5 7) 'evenp)
((1) (5 7))
而不是member-if
和ldiff
,我们也可以cut
从this answer到Idiomatic way to group a sorted list of integers?。{/ p>