Lisp 语言中 split 的实现方法与效率

来源:互联网 发布:js求字符串字节长度 编辑:程序博客网 时间:2024/05/17 22:57

在字符串处理中,各种高级语言均支持 split 函数,比如 vb、C#、Python、java 等,split 的基本功能是用一个短字符串去分割一个长字符串,并返回分割后的数组。
例如: (split "I Love You" " ") 用空格切割字符串,返回 ("I" "Love" "You")
遗憾的是 Lisp 中并未包括此函数,本文将讨论 split 在 lisp 语言中的实现方法与效率。


自定义函数

以下两个函数分别是明经和小东论坛里,大师们用 VLisp 函数实现的 split 方法
其中参数为: str — 待处理的长字符串; p — 为分割关键词

;;来自明经,该函数使用频率较高,互相引用频繁,原作者不详(defun Split1 (str p / pa sl xn)(setq xn (1+ (strlen p)))(while (setq pa (vl-string-search p str))    (setq sl (cons (substr str 1 pa) sl)        str (substr str (+ pa xn))))(reverse (cons str sl)))
;;来自小东, Gu_xl 的 vlstring->list(Defun Split2 (str p / lst e)(setq str (strcat str p))(while (vl-string-search p str)    (setq lst (append lst (list (substr str 1 (vl-string-search p str)))))    (setq str (substr str (+ (1+ (strlen p)) (vl-string-search p str)))))(if lst (mapcar '(lambda (e) (vl-string-trim " " e)) lst)))

以上函数算法基本相同:即用 vl-string-search 函数搜索 p 在 str 中出现的位置,再用 substr 按位置切割 str 字符串,并将返回值重新组合为一个 list 表。
测试:

命令: (split1 “I Love You” ” “) 返回 (“I” “Love” “You”)
命令: (split2 “I Love You” ” “) 返回 (“I” “Love” “You”)

如果到这里结束的话,就属于灌水的博客,显然本文的目的并不在此。再看下面出现汉字时的调用:

命令: (Split1 “粃糠abczyx” “z”) 返回 (“? “糠abc” “yx”)
命令: (Split2 “笨賊a\shit” “\”) 返回 (“笨? “a” “shit”)

返回值出现 bug,说明函数对字符的处理有漏洞。


修正 SPLIT

经测试,上面函数 bug 出现原因是 vl-string-search 函数造成的。
该函数在处理英文字符时,按一个字节 ascii 码搜索,处理汉字时,是按两个字节的 ascii 码,而不是将汉字视为一个整体。

命令: (vl-string->list “z”) 返回 (122)
命令: (vl-string->list “粃”) 返回 (187 122)

当函数的参数 str 里的汉字出现了参数 p 中英文字符的 ascii 码时,字符分割就会出现问题。

根据这个现象,增加对汉字的判断,如果首字节 ASCII 大于 128 时,即为汉字,则跳过两个字节。修正后的 split 函数如下:

(defun Split3 (str p / pa sl xn f)(setq xn (1+ (strlen p)) f 0)(while (setq pa (vl-string-search p str f))    (if (< (vl-string-elt str (1- pa)) 128)        (setq sl (cons (substr str 1 pa) sl)            str (substr str (+ pa xn)) f 0)        (setq f (1+ pa))    ))(reverse (cons str sl)))

正则 SPLIT

关于 AutoCAD 以及自带的 LISP 语言对中文字符处理的先天性缺陷,在其他语言中很少存在,现用正则表达式来设计一个 split 函数:

(defun Split (s p / L r)(setq r (vlax-create-object "vbscript.regexp"))(vlax-put-property r 'Global 1)(vlax-put-property r 'Pattern (strcat "([^" p "]+)"))(vlax-for x (vlax-invoke r 'Execute s)(setq L (cons(vla-get-Value x) L)))(vlax-release-object r)(reverse L))

用正则很简洁,只需要告诉正则对象的匹配语法即可。而且这个函数还有一个实用的功能,可以支持多个关键字分割,只需要用 “|”将关键字分开即可。

测试:

命令: (Split “粃糠abczyx” “z”) 返回 (“粃糠abc” “yx”)
命令: (split “abcfarecadefge” “c|f”) 返回 (“ab” “are” “ade” “ge”)


效率测试

创建一个长度为十万的字符串,对比测试一下 vlisp 和 正则 函数的分割效率。

(defun c:test( / i AA)(setq i 0 AA "")(while (< i 100000) (setq AA (strcat AA (itoa (setq i (1+ i))) ",")))(setq time0 (getvar "date"))(Split3 AA ",") ;;调用 VL 函数(setq time1 (getvar "date"))(Split AA ",")  ;;调用正则函数(setq time2 (getvar "date"))(princ (strcat "\nVLisp函数耗时: " (rtos (* 86400 (- time1 time0)) 2 4) " 秒"))(princ (strcat "\n正则函数耗时: " (rtos (* 86400 (- time2 time1)) 2 4) " 秒"))(princ))

运行结果:

VLisp函数耗时: 45.269 秒
正则函数耗时: 0.722 秒

可见 VL 函数和正则表达式对字符串的操作效率完全没有可比性。实际上,即使在 vb 或 c 中,用自带的 split 函数也比不上用正则来实现 split 功能的效率,这就是 ActiveX/COM 的优越性。

唯一需要注意的是,在 AutoLisp 和 正则表达式中,不约而同的定义 “\” 为转义字符,所以如果要以单字符“\” 为切割字符,应该用四个 “\\” 来表示,第一次转义为 CAD 字符,第二次转义为正则字符。
例如:

命令: (Split "笨賊a\\shit" "\")

分割字符为单个斜杠是不行的,需要用四个斜杠表示:

命令: (Split "笨賊a\\shit" "\\\\")

原创粉丝点击