在lisp中解析字符串中的数字

时间:2009-09-29 23:34:40

标签: parsing text lisp common-lisp

这是一个简短的问题:
输入:字符串列表,每个字符串包含数字
(“3.4 5.4 1.2 6.4”“7.8 5.6 4.3”“1.2 3.2 5.4”)

输出:数字列表
(3.4 5.4 1.2 6.4 7.8 5.6 4.3 1.2 3.2 5.4)

这是我尝试编码:

(defun parse-string-to-float (line &optional (start 0))
  "Parses a list of floats out of a given string"
  (if (equalp "" line)
    nil
    (let ((num (multiple-value-list (read-from-string (subseq line start)))))
      (if (null (first num))
        nil
        (cons (first num) (parse-string-to-float (subseq line (+ start (second num)))))))))

(defvar *data* (list "  3.4 5.4 1.2 6.4" "7.8 5.6 4.3" "1.2 3.2 5.4"))

(setf *data* (format nil "~{~a ~}" *data*))

(print (parse-string-to-float *data*))

===> (3.4 5.4 1.2 6.4 7.8 5.6 4.3 1.2 3.2 5.4)

但是,对于相当大的数据集,这是一个缓慢的过程。我猜测递归并不是那么紧,我正在做一些不必要的事情。有任何想法吗?

此外,宏项目涉及获取具有由关键字分隔的各种数据部分的输入文件。示例 -

%FLAG START_COORDS
1   2   5   8   10   12  
%FLAG END_COORDS  
3   7   3   23   9   26
%FLAG NAMES
ct  re  ct  cg  kl   ct

...等 我正在尝试使用%FLAG后面的关键字作为键解析哈希表,并根据我正在解析的特定关键字将值存储为数字或字符串列表。对于已经完成这类工作的图书馆的任何想法,或者在lisp中解决这个问题的简单方法?

4 个答案:

答案 0 :(得分:9)

这不是您希望以递归方式开始的任务。相反,请使用LOOPCOLLECT子句。例如:

(defun parse-string-to-floats (line)
  (loop
    :with n := (length line)
    :for pos := 0 :then chars
    :while (< pos n)
    :for (float chars) := (multiple-value-list
            (read-from-string line nil nil :start pos))
    :collect float))

此外,您可能需要考虑使用WITH-INPUT-FROM-STRING代替READ-FROM-STRING,这会让事情变得更简单。

(defun parse-string-to-float (line)
  (with-input-from-string (s line)
    (loop
      :for num := (read s nil nil)
      :while num
      :collect num)))

至于性能,您可能希望进行一些分析,并确保实际编译功能。

添加:

编辑:您需要注意的一件事是,如果您不确定字符串的来源,读者可能会引入安全漏洞。有一个读取宏#.,当从字符串中读取时,它可以允许评估跟随它的任意代码。保护自己的最佳方法是将*READ-EVAL*变量绑定到NIL,这会使读者在遇到#.时发出错误信号。或者,您可以使用Rainer Joswig mentions in his answer

之一的专用库

答案 1 :(得分:8)

解析单个字符串

(defun parse-string-to-floats (string)
  (let ((*read-eval* nil))
    (with-input-from-string (stream string)
      (loop for number = (read stream nil nil)
            while number collect number))))

处理字符串列表并返回单个列表

(defun parse-list-of-strings (list)
  (mapcan #'parse-string-to-floats list))

示例

CL-USER 114 > (parse-list-of-strings (list "1.1 2.3 4.5" "1.17 2.6 7.3"))
(1.1 2.3 4.5 1.17 2.6 7.3)

注意

一项代价高昂的操作是READ从流中读取浮点值。像PARSE-NUMBER这样的库可能更有效 - 一些Common Lisp实现也可能具有READ-FLOAT / PARSE-FLOAT功能。

答案 2 :(得分:2)

同样为了表现,请尝试

(declare (optimize (speed 3)))
在你的defun里面。一些lisps(例如SBCL)将打印有关无法优化的有用信息,以及未进行此优化的估计成本

答案 3 :(得分:2)

至于性能,请至少尝试测量内存分配。我猜所有的性能都被内存分配和GC吃掉了:你用subseq分配了很多大字符串。例如,(time(parse-string-to-float ..))将显示代码花费的时间,GC中的内存量以及分配的内存量。

如果是这种情况,那么使用string-stream(比如with-input-from-string)来降低GC压力。