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


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


VL函数实现的 VL-SPLIT

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

函数1

;;来自明经,该函数使用频率较高,互相引用频繁,原作者不详
(defun vl-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))
)

函数2

;;来自小东, Gu_xl 的 vlstring->list
(Defun vl-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,说明函数对字符的处理有漏洞。


修正后的 VL-SPLIT

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

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

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

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

(defun vl-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))
)

正则表达式实现的 RG-SPLIT

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

(defun rg-Split (s p / L r)
(setq r (vlax-create-object "vbscript.regexp"))
(vlax-put-property r 'Global 1)
(vlax-put-property r 'Pattern p)
(read (strcat "(\"" (vlax-invoke r 'Replace s "\" \"") "\")"))
)

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

测试:

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


SPLIT 效率测试对比

创建一个长度为十万的字符串,对比测试一下 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"))
(vl-Split3 AA ",") ;;调用 VL 函数
(setq time1 (getvar "date"))
(rg-Split AA ",")  ;;调用正则函数
(setq time2 (getvar "date"))
(princ (strcat "\nvl-split 函数耗时: " (rtos (* 86400 (- time1 time0)) 2 4) " 秒"))
(princ (strcat "\nrg-split 函数耗时: " (rtos (* 86400 (- time2 time1)) 2 4) " 秒"))
(princ)
)

运行结果:

vl-split 函数耗时: 28.615 秒
rg-split 函数耗时: 0.047 秒

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

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

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

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

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

  • 8
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yxp_xa

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值