[新手上路]批处理新手入门导读[视频教程]批处理基础视频教程[视频教程]VBS基础视频教程[批处理精品]批处理版照片整理器
[批处理精品]纯批处理备份&还原驱动[批处理精品]CMD命令50条不能说的秘密[在线下载]第三方命令行工具[在线帮助]VBScript / JScript 在线参考
返回列表 发帖

[文本处理] 求字符串长度,简短高效的批处理代码(多种算法)

这是迄今为至,我多年来看过的批处理代码中,见到的最优美的一段代码,不和大家分享一下实在不好意思。
原以为,求字符串长度的方法在二分法和查表法后不会有突破了,但我发现时我的思想是太懒了。

二分回溯法(现在更正下,准确的叫法应该是优化的二分法)求字符串长度:

原帖:http://www.dostips.com/forum/viewtopic.php?f=3&t=1429
  1. setlocal enabledelayedexpansion
  2. set "$=!%1!#"
  3. set len=&for %%a in (4096 2048 1024 512 256 128 64 32 16 8 4 2 1)do if !$:~%%a^,1!. NEQ . set/a len+=%%a&set $=!$:~%%a!
  4. endlocal&If %2. neq . (set/a%2=%len%)else echo %len%
复制代码
关于求字符串长度,以前论坛里讨论过不少,但就代码长度,支持最长字符串,和执行耗时来综合考虑,
我“个人认为”都没有超越上面这段代码。


把原代码小修小补增加了30多K的字节(原帖是联合256位的表),可以减少相当于大概2~7次set 赋值命令所耗费的时间(效率提高8%~30%):
  1. :strlen <stringVarName> [retvar]
  2. :: 思路: 二分回溯联合查表法
  3. :: 说明: 所求字符串大小范围 0K ~ 8K;
  4. ::    stringVarName ---- 存放字符串的变量名
  5. ::    retvar      ---- 接收字符长度的变量名
  6. setlocal enabledelayedexpansion
  7. set "$=!%1!#"
  8. set N=&for %%a in (4096 2048 1024 512 256 128 64 32 16)do if !$:~%%a!. NEQ . set/aN+=%%a&set $=!$:~%%a!
  9. set $=!$!fedcba9876543210&set/aN+=0x!$:~16,1!
  10. endlocal&If %2. neq . (set/a%2=%N%)else echo %N%
复制代码
对于上面代码
如果所求字符串长度不超过4096:
可以将4096 2048 1024 512 256 128 64 32 16改为2048 1024 512 256 128 64 32 16

如果所求字符串长度不超过2048:
可以将4096 2048 1024 512 256 128 64 32 16改为      1024 512 256 128 64 32 16
以此类推。。。

=============================================================
测试一下效率
  1. @echo off&setlocal enabledelayedexpansion
  2. :: 将strlen函数内敛到变量_strlen中,测试字符串长度小于4096;入口参数#1,返回变量##
  3. set "_strlen=set $=^!#1^!#&set ##=&(for %%a in (2048 1024 512 256 128 64 32 16)do if ^!$:~%%a^!. NEQ . set/a##+=%%a&set $=^!$:~%%a^!)&set $=^!$^!fedcba9876543210&set/a##+=0x^!$:~16,1^!"
  4. :: 生产一个长度为4096字符个数的字符串
  5. set str=.&for /l %%a in (1 1 12)do set str=!str!!str!
  6. :: 测试一下速度,4000次
  7. set bt=%time%
  8. for /l %%a in (1 1 4000)do (
  9.         set #1=!str:~-%%a!
  10.         (%_strlen%)
  11.         echo 长度:!##!
  12. )
  13. set et=%time%
  14. :: 计算花费时间
  15. set /a ct=1!et:~-5,2!!et:~-2!-1!bt:~-5,2!!bt:~-2!,ct+=-6000*("ct>>31")
  16. echo ---------------------&echo 开始时间:%bt%&echo 结束时间:%et%
  17. echo 4000次测试用时:%ct% 跑秒&echo -----------------------
  18. pause
复制代码
7

评分人数

在这里把论坛里求字符串长度的帖子做一个索引,大家可以把这些方法相互做个比较,
也许你能从中提炼出精华得到更代码更简洁效率更高效的方法也不一定。

求字符串长度练习(各种方法,真是大杂烩,很多带有goto循环的和调用三方的方法)

http://www.bathome.net/viewthread.php?tid=1480

当字符串较长时不得不用的方法——二分法
http://bbs.bathome.net/viewthread.php?tid=4219

对字符串长度有限制的查表法:
(9位和40位)http://www.bathome.net/viewthread.php?tid=3136 (6楼)
(16位)http://www.bathome.net/viewthread.php?tid=1249 (15楼)
(255位和1024位)http://www.bathome.net/viewthread.php?tid=5994
关于查表法还有很多相关代码,但思想都来源于9位法,不再列举。
======================================================

下面对这些求字符串长度的典型方法做个大致的汇总(代码均来自上面的索引贴子)
带有goto循环的方法,使得效率极为低下,这里不列举。
调用三方工具求长度的方法(如findstr/o 法,vbs法,for/f +dir法,xcopy法)效率更极为低下,这里不列举

方法一:思路最直接,代码最简短,但效率最低的方法(所求字符串较长时不宜采用)

原帖:http://www.bathome.net/viewthread.php?tid=1480
  1. set/p str=input a string:
  2. set StrMAX=1000
  3. if not defined str set num=0&goto :ok
  4. for /l %%a in (0,1,%StrMAX%) do if "!str:~%%a,1!"=="" set num=%%a&goto :ok
  5. :ok
  6. echo 长度=!num!
复制代码
方法二:适合求长字符串的二分法
原帖:http://bbs.bathome.net/viewthread.php?tid=4219
  1. set /p s=please input a string:
  2. setlocal enabledelayedexpansion
  3. set /a n=8189*2,max=1&set "var="
  4. for /l %%a in (1 1 14) do (
  5.    if defined var set /a n=var
  6.    set /a n/=2
  7.    for %%i in (!n!) do (
  8.       if "!s:~%%i,1!"=="" (set /a var=n) else (
  9.          set s=!s:~%%i!&set /a max+=%%i,var-=%%i
  10. )))  
  11. endlocal&echo 长度:%max%
复制代码
方法2.X 改进的二分搜索法(效率提高不少)
原帖:http://www.dostips.com/forum/viewtopic.php?f=3&t=1429
  1. set /p str=input a string
  2. SETLOCAL ENABLEDELAYEDEXPANSION
  3.     set "str=A!str!"
  4.     set len=0
  5.     for /L %%A in (12,-1,1) do (
  6.         set /a "len|=1<<%%A"
  7.         for %%B in (!len!) do if "!str:~%%B,1!"=="" set /a "len&=~1<<%%A"
  8. ENDLOCAL&echo 长度:%len%
复制代码
方法三:查表法
当所求字符串长度小于16时,速度最快最简短的查表法:
原帖:http://www.bathome.net/viewthread.php?tid=1249 (15楼)
  1. set str=!str!fedcba9876543210&set/a len=0x!str:~15,1!
  2. echo 长度%len%
复制代码
当所求字符串字符数小于256时,速度最快(和上面代码效率相当)但代码较长的查表法
(一般说来,若用较短的程序生产该512表,至少需要16次set赋值语句的执行):
原帖: http://www.bathome.net/viewthread.php?tid=5994
  1. @echo off&setlocal enabledelayedexpansion
  2. set/p str=input a string:
  3. set "$=000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f606162636465666768696a6b6c6d6e6f707172737475767778797a7b7c7d7e7f808182838485868788898a8b8c8d8e8f909192939495969798999a9b9c9d9e9fa0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3b4b5b6b7b8b9babbbcbdbebfc0c1c2c3c4c5c6c7c8c9cacbcccdcecfd0d1d2d3d4d5d6d7d8d9dadbdcdddedfe0e1e2e3e4e5e6e7e8e9eaebecedeeeff0f1f2f3f4f5f6f7f8f9fafbfcfdfeff%str%%str%"
  4. set/a len=0x!$:~-512,2!
  5. echo 长度:%len%
  6. pause
复制代码
方法四:等长替换计算法
原帖: 见本贴13楼lllsoslll的代码
(效率低于纯查表法,仅次于二分回溯法,优势在于表的生产不是很占用时间,且代码较短)
注意注意,"SET $=123456789$" 中,等号后的空白字符不是空格符!,而是ascii码为127的那个del字符;
  1. set/p #1=string:?
  2. SET $=123456789$&FOR /L %%a in (1 1 3)Do SET $=!$!!$!!$!!$!!$!!$!
  3. SET $=!#1!`$!$!&SET $=!$:~0,2376!&SET $=!$:*`$=!&SET/A"$=-1,##=2372-(!$:123456789$=11+!+!$:~-1!)"
  4. echo length=!##!
复制代码
其他方法:待续。。。

--------------------------------- 更新1 -------------------------------
下面算法思想是基于16查表法,先求出待测字符串长度对16的"倍数值",再求对16的余数值;最后相加
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. :: 要测试的字符串
  4. set $1=123456789012345678901234567890123456789012345670
  5. :: 该字符串长度不超过256且大于0
  6. set $2=256
  7. rem ----------------------------------------------
  8. rem ----------------------------------------------
  9. echo 长度为 %$%
  10. pause
复制代码
当$2指定为256,这段代码的耗时相当于10次set赋值操作的耗时,
对于短字符串求长度,效率和代码体积上,很实用(效率很高,且算法简单,容易理解记忆)
---------------------------------------------------------------------

如果把16位表换为256为表, 那么对于小于4096长度以内的字符串,由于512位的表占用了不少空间, 会影响 外部环境 赋值,取值操作效率,
关于这个问题, 有时间我再做测试,

预期这个效率兴许会比1楼代码效率高。。。
经测试, 对于长字符串,该方法较比1楼的代码 效率提高了25%左右

TOP

有一种直觉,几乎接近真实了
就求字符串长度来说,还有比一楼更快代码更简洁的代码。

TOP

同findstr类似,又一个外部命令求字符串长度的方法(适合于纯英文字符),连续的宽字符被视为1个字符;
  1. @echo off
  2. setlocal enabledelayedexpansion
  3. set str=www.bathome.net
  4. set n=0
  5. for /f "delims=" %%a in ('cmd/u/cecho !str!^|more')do set/a n+=1
  6. set n
  7. pause
复制代码
如果要给英文字符串中每个字符后加换行符,这是个简洁的方案;

TOP

返回列表