【问题标题】:How can I capture the results of splitting a string in elisp?如何捕获在 elisp 中拆分字符串的结果?
【发布时间】:2012-10-11 05:08:44
【问题描述】:

我在 elisp 中工作,我有一个代表项目列表的字符串。字符串看起来像

"apple orange 'tasty things' 'my lunch' zucchini 'my dinner'"

我正在尝试将其拆分为

("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")

This is a familiar problem。我解决它的障碍不是关于正则表达式,而是更多关于 elisp 的细节。

我想要做的是像这样运行一个循环:

  • (while (< (length my-string) 0) do-work)

do-work 在哪里:

  • 将正则表达式\('[^']*?'\|[[:alnum:]]+)\([[:space:]]*\(.+\) 应用于my-string
  • \1 附加到我的结果列表中
  • my-string重新绑定到\2

但是,我不知道如何让 split-stringreplace-regexp-in-string 做到这一点。

如何将此字符串拆分为可以使用的值?

(或者:“我还没有找到哪个内置的emacs函数呢?”)

【问题讨论】:

    标签: regex emacs elisp


    【解决方案1】:

    类似的东西,但没有正则表达式:

    (defun parse-quotes (string)
      (let ((i 0) result current quotep escapedp word)
        (while (< i (length string))
          (setq current (aref string i))
          (cond
           ((and (char-equal current ?\ )
                 (not quotep))
            (when word (push word result))
            (setq word nil escapedp nil))
           ((and (char-equal current ?\')
                 (not escapedp) 
                 (not quotep))
            (setq quotep t escapedp nil))
           ((and (char-equal current ?\')
                 (not escapedp))
            (push word result)
            (setq quotep nil word nil escapedp nil))
           ((char-equal current ?\\)
            (when escapedp (push current word))
            (setq escapedp (not escapedp)))
           (t (setq escapedp nil)
            (push current word)))
          (incf i))
        (when quotep
          (error (format "Unbalanced quotes at %d"
                         (- (length string) (length word)))))
        (when word (push result word))
        (mapcar (lambda (x) (coerce (reverse x) 'string))
                (reverse result))))
    
    (parse-quotes "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
    ("apple" "orange" "tasty things" "my lunch" "zucchini" "my dinner")
    
    (parse-quotes "apple orange 'tasty thing\\'s' 'my lunch' zucchini 'my dinner'")
    ("apple" "orange" "tasty thing's" "my lunch" "zucchini" "my dinner")
    
    (parse-quotes "apple orange 'tasty things' 'my lunch zucchini 'my dinner'")
    ;; Debugger entered--Lisp error: (error "Unbalanced quotes at 52")
    

    奖励:它还允许使用“\”转义引号,如果引号不平衡(到达字符串末尾,但未找到打开的引号的匹配项),则会报告它。

    【讨论】:

    • 哦,嘿,现在正确地解析东西。这个答案很有教育意义。 :)
    • 提高了我的知识,是唯一完全符合答案规范的答案 - 如此接受。 谢谢。 :D
    • 三年后这个答案+1 - 我在整个互联网上的其他任何地方都找不到这个:D 有没有提供这个的包?真的应该有。编辑:显然split-string-and-unquote 几乎可以做到这一点,但我不能让它也做单引号......嗯。
    【解决方案2】:

    这是使用临时缓冲区实现算法的简单方法。我不知道是否有办法使用replace-regexp-in-stringsplit-string 来做到这一点。

    (defun my-split (string)
      (with-temp-buffer
        (insert string " ")     ;; insert the string in a temporary buffer
        (goto-char (point-min)) ;; go back to the beginning of the buffer
        (let ((result nil))
          ;; search for the regexp (and just return nil if nothing is found)
          (while (re-search-forward "\\('[^']*?'\\|[[:alnum:]]+\\)\\([[:space:]]*\\(.+\\)\\)" nil t)
            ;; (match-string 1) is "\1"
            ;; append it after the current list
            (setq result (append result (list (match-string 1))))
            ;; go back to the beginning of the second part
            (goto-char (match-beginning 2)))
          result)))
    

    例子:

    (my-split "apple orange 'tasty things' 'my lunch' zucchini 'my dinner'")
      ==> ("apple" "orange" "'tasty things'" "'my lunch'" "zucchini" "'my dinner'")
    

    【讨论】:

      【解决方案3】:

      您不妨看看split-string-and-unquote

      【讨论】:

        【解决方案4】:

        如果你经常操作字符串,你应该通过包管理器安装 s.el 库,它在一致的 API 下引入了大量的字符串实用函数。对于此任务,您需要函数s-match,其可选的第三个参数接受起始位置。然后,你需要一个正确的正则表达式,试试:

        (concat "\\b[a-z]+\\b" "\\|" "'[a-z ]+'")
        

        \| 表示匹配构成单词的字母序列(\b 表示单词边界)或引号内的字母和空格序列。然后使用loop:

        ;; let s = given string, r = regex
        (loop for start = 0 then (+ start (length match))
              for match = (car (s-match r s start))
              while match 
              collect match)
        

        出于教育目的,我还使用递归函数实现了相同的功能:

        ;; labels is Common Lisp's local function definition macro
        (labels
            ((i
              (start result)
              ;; s-match searches from start
              (let ((match (car (s-match r s start))))
                (if match
                    ;; recursive call
                    (i (+ start (length match))
                       (cons match result))
                  ;; push/nreverse idiom
                  (nreverse result)))))
          ;; recursive helper function
          (i 0 '()))
        

        由于 Emacs 缺少 tail call optimization,因此在大列表上执行它可能会导致 stack overflow。因此你可以用do 宏重写它:

        (do* ((start 0)
              (match (car (s-match r s start)) (car (s-match r s start)))
              (result '()))
            ((not match) (reverse result))
          (push match result)
          (incf start (length match)))
        

        【讨论】:

        • 虽然这很有帮助,但同样重要的是要注意 s.el 是一个不随 emacs 提供的包。更好的答案,尤其是公认的答案,以相同或更低级别的代码复杂性完成任务,但不涉及第三方包。您提出的答案在多个方面更为复杂。
        • s-match + loop 宏并不复杂。请查看更新的答案,我试图澄清。
        猜你喜欢
        • 2015-04-21
        • 1970-01-01
        • 2023-02-10
        • 2014-10-12
        • 1970-01-01
        • 1970-01-01
        • 2012-02-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多