1

我有以下元素列表

("(aviyon" "213" "flyingman" "no))") as list

我想要的是我想拆分这个包含使用括号作为拆分器的字符串的列表,但也想在不破坏顺序的情况下将这些括号包含在一个新列表中

我想要的新列表输出(或修改的相同列表)

("(" "aviyon" "213" "flyingman" "no" ")" ")") 

我来自命令式语言,这将是 Java 或 C++ 的 15 分钟工作。但是在这里我被困住了该怎么做。我知道我必须

1-在循环中从列表中获取元素

我认为这是完成的(nth 1 '(listname) )

2-分开而不删除分隔符放入新列表

我找到了诸如 SPLIT-SEQUENCE 之类的功能,但我不能不删除它并且不破坏原始顺序。

任何帮助,将不胜感激。

4

3 回答 3

4

您可以使用 cl-ppcre 库来完成这项工作。

例如:

CL-USER> (ql:quickload :cl-ppcre)

CL-USER> (cl-ppcre:split "([\\(\\)])" "(aviyon" :with-registers-p t)
("" "(" "aviyon")
CL-USER> (cl-ppcre:split "([\\(\\)])" "no))" :with-registers-p t)
("no" ")" "" ")")
CL-USER> 

但是,它会在列表中生成空字符串。使用remove-if函数摆脱它们:

CL-USER> (defun empty-string-p (s) (string= s ""))
EMPTY-STRING-P
CL-USER> (remove-if 'empty-string-p
                    (list "no" ")" "" ")"))
("no" ")" ")")

最后,您可以构造一个同时执行这两种功能的函数,并在imperative循环中运行它(是的,Common Lisp 并不像许多人认为的那样实用):

CL-USER> (defun remove-empty-strings (l)
           (remove-if 'empty-string-p l))
REMOVE-EMPTY-STRINGS
CL-USER> (defun split (s)
           (cl-ppcre:split "([\\(\\)])"
                           s
                           :with-registers-p t))
SPLIT
CL-USER> (defparameter *the-list* '("(aviyon" "213" "flyingman" "no))"))
*THE-LIST*
CL-USER> (loop for item in *the-list*
               for splitted = (split item)
               for cleaned = (remove-empty-strings splitted)
               append cleaned)
("(" "aviyon" "213" "flyingman" "no" ")" ")")
于 2018-11-25T08:44:20.200 回答
2

让我们有另一个答案,没有外部库。就像你已经做过的那样,我们可以将问题分成更小的部分:

  1. 定义一个从字符串构建标记列表的函数,all-tokens
  2. 将此函数应用于输入列表中的所有字符串,并连接结果:

    (mapcan #'all-tokens strings)
    

第一部分,获取一个状态并从中构建一个列表,看起来像一个unfold操作(变形)。

在 Lisp中调用的折叠(catamorphism)reduce从值列表和函数(以及可选的初始值)构建一个值。对偶运算unfold接受一个值(状态)、一个函数,并生成一个值列表。在 的情况下unfold,step 函数接受一个状态并返回新状态以及结果列表。

在这里,让我们将状态定义为 3 个值:一个字符串、字符串中的起始位置以及到目前为止已解析的令牌堆栈。我们的 step 函数next-token返回下一个状态。

 ;; definition follows below
 (declare (ftype function next-token))

从字符串中获取所有标记的主函数只计算一个固定点:

(defun all-tokens (string)
  (do (;; initial start value is 0
       (start 0)
       ;; initial token stack is nil
       (tokens))

      ;; loop until start is nil, then return the reverse of tokens
      ((not start) (nreverse tokens))

    ;; advance state
    (multiple-value-setq (string start tokens)
      (next-token string start tokens))))

我们需要一个辅助函数:

(defun parenthesisp (c)
  (find c "()"))

阶跃函数定义如下:

(defun next-token (string start token-stack)
  (let ((search (position-if #'parenthesisp string :start start)))
    (typecase search
      (number
       ;; token from start to parenthesis
       (when (> search start)
         (push (subseq string start search) token-stack))
       ;; parenthesis
       (push (subseq string search (1+ search)) token-stack)
       ;; next state
       (values string (1+ search) token-stack))
      (null
       ;; token from start to end of string
       (when (< start (1- (length string)))
         (push (subseq string start) token-stack))
       ;; next-state
       (values string nil token-stack)))))

您可以尝试使用单个字符串:

(next-token "(aviyon" 0 nil)
"(aviyon"
1
("(")

如果您获取结果状态值并重用它们,您将拥有:

(next-token "(aviyon" 1 '("("))
"(aviyon"
NIL
("aviyon" "(")

而这里,第二个返回值是NIL,它结束了生成过程。最后,你可以这样做:

(mapcan #'all-tokens '("(aviyon" "213" "flyingman" "no))"))

这使:

("(" "aviyon" "213" "flyingman" "no" ")" ")")

all-tokens上面的代码在对了解太多的意义上并不完全通用next-token:您可以重写它以获取任何类型的状态。您还可以使用相同的机制处理字符串序列,方法是在状态变量中保留更多信息。此外,在真正的词法分析器中,您不想反转整个标记列表,您将使用队列来提供解析器。

于 2018-11-26T10:32:21.533 回答
0

解决方案

既然你不理解亚历山大的解决方案,而且我还是写了我的解决方案:

;; load two essential libraries for any common lisper
(ql:quickload :cl-ppcre)
(ql:quickload :alexandria)
;; see below to see how to install quicklisp for `ql:quickload` command
;; it is kind of pythons `import` and if not install `pip install`
;; in one command for common-lisp

(defun remove-empty-string (string-list) 
  (remove-if #'(lambda (x) (string= x "")) string-list))


(defun split-parantheses-and-preserve-them (strings-list)
  (remove-empty-string 
  (alexandria:flatten 
    (mapcar #'(lambda (el) (cl-ppcre:split "(\\(|\\))" 
                                           el 
                                           :with-registers-p t)) 
            strings-list))))

 ;; so now your example
 (defparameter *list* '("(aviyon" "213" "flyingman" "no))"))

 (split-parantheses-and-preserve-them *list*)
 ;; returns:
 ;; ("(" "aviyon" "213" "flyingman" "no" ")" ")")

这是如何工作的

(cl-ppcre:split "(\\(|\\))" a-string)(用or 分割字符串)。因为在正则表达式模式中()用于捕获匹配 - 就像这里一样(外部括号捕获) - 你必须逃避它们。\\(\\)。因此,cl-ppcre:split您可以通过正则表达式模式拆分普通 lisp 中的任何字符串。由 Edi Weitz 编写的超酷且超高效的软件包。他为 common lisp 编写了几个超级复杂的包——它们在社区中也被称为 ediware 或 edicls。顺便说一句 - cl-ppcre 比正则表达式的黄金标准:perl 正则表达式引擎更高效、更快!

:with-regiesters-p t然后选项保留匹配的分隔符 - 必须通过括号捕获,如下所示:(<pattern>)在模式中。

mapcarthis 在列表上以将其应用于字符串列表中的每个字符串元素。

但是,之后得到的是列表列表。(每个内部列表都包含列表中每个字符串元素的拆分结果)。

将列表展平alexandria:flatten。对于许多不在 lisp 标准中的函数,但您认为它们是基本的——比如展平列表——总是首先在亚历山大港中查找——大多数情况下它有你想要的函数——它是一个巨大的库。这就是为什么你无论如何都需要它作为一个普通的 lisper ;) 。

但是,仍然会有空字符串被删除。这就是为什么我写了remove-empty-stringwhich uses remove-if- which 以及remove-if-not列表的标准过滤功能。它需要一个谓词函数 -(lambda (x) (string= x ""))如果字符串为空字符串,则为 T,否则为 NIL。它删除了我们函数中生成的扁平列表中的所有元素,这些元素是空字符串。在其他语言中,它会被命名filter,但是是的 - 有时 common-lisp 中的函数名称选择得不是很好。有时我认为我们应该创建别名并转移到它们并保留旧名称以实现向后兼容性。Clojure 有更好的函数名称......也许 cl 人应该超越 clojure 函数名称......

快捷方式

@Alexander Artemenko 写的正是我的解决方案——他是第一位的。我要补充一点:如果您对 common lisp 很陌生,也许您不知道如何使用 quicklisp。在终端(linux或macos)中执行:

wget https://beta.quicklisp.org/quicklisp.lisp

否则从地址手动在windows中下载。

我把它放在~/quicklisp文件夹里。

然后在 clisp 或 sbcl 中执行:

(load "~/quicklisp/quicklisp.lisp") ;; just path to where downloaded
;; quicklisp.lisp file is!

;; then install quicklisp:
(quicklisp-quickstart:install)

;; then search for cl-ppcre
(ql:system-apropos "cl-ppcre")

;; then install cl-ppcre
(ql:quickload "cl-ppcre")

;; and to autoload everytime you start sbcl or clisp
;; in linux or mac - sorry I don't now windows that well
;; I have the opinion every programmer should us unix
;; as their OS
;; you have to let quicklisp be loaded when they start
;; by an entry into the init file
;; mostly located in ~/.sbclrc or ~/.clisprc.slip or such ...
;; respectively.
;; quicklisp does an entry automatically if you do:
(ql:add-to-init-file)

;; after installation do:
(quit)

;; If you then restart sbcl or clisp and try:
(ql:quickload :cl-ppcre)
;; it should work, - if not, you have to manually load
;; quicklisp first
(load "~/quicklisp/setup.lisp") ;; or wherever quicklisp's
;; setup.lisp file has been stored in your system!
;; and then you can do
(ql:quickload :cl-ppcre)

;; to install alexandria package then, do
(ql:quickload :alexandria) ;; or "alexandria"

;; ql:quickload installs the package from quicklisp repository,
;; if it cannot find package on your system.

;; learn more about quicklisp, since this is the package
;; manager of common lisp - like pip for python
于 2018-11-25T09:15:52.963 回答