【问题标题】:Split a string even if the last character is a delimiter即使最后一个字符是分隔符也拆分字符串
【发布时间】:2014-06-02 17:57:33
【问题描述】:

我想删除字符串末尾的一些字符。

我做了这个功能:

(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

但如果字符串的结尾是分隔符,它就不起作用,这就是我尝试这样做的原因。

我做错了什么? 提前致谢。

【问题讨论】:

  • 根据您接受的答案,听起来您更像是想知道如何从右侧修剪字符串,但您的原始问题标题是“拆分字符串,即使最后一个字符是分隔符”。你能澄清一下你到底在找什么吗?

标签: string split lisp common-lisp


【解决方案1】:

简单的方法

只需使用string-right-trim:

(string-right-trim " ,.;!?/" s)

你的错误

如果您将空字符串传递给del-delimiter-at-end,您将把-1 作为第二个参数传递给char

您的代码

  1. 没有理由做(eq (delimiterp ...) nil);只需使用(delimiterp ...) 代替(并切换子句!)

  2. 当您只有两个子句且每个子句只有一种形式时,使用 if 而不是 cond 是模式惯用的。

  3. 你递归调用subseq,这意味着你不仅无缘无故分配内存,你的算法在字符串长度上也是二次的。

【讨论】:

  • 再次感谢您的回复和建议!我只想提一下,错误来自我没有用新值设置字符串,我忘记了 (setq string (del-delimiter-at-end (string)))
【解决方案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
;=> ""

使用complementposition

有些人可能会指出 position-if-not 已被弃用。如果不想使用,可以使用complementposition-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 带补码的参数是首选。

也就是说,positionposition-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))

虽然这适用于所有类型的序列,但对于列表来说效率不是很高,因为subseqposition 等都必须遍历列表直到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-ifldiff,您还可以将cutthis answer 转换为Idiomatic way to group a sorted list of integers?

【讨论】:

    猜你喜欢
    • 2017-04-23
    • 1970-01-01
    • 2017-04-01
    • 1970-01-01
    • 2013-02-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多