批处理之家's Archiver

batman 发表于 2008-7-26 01:40

【练习-002】批处理查找字符数最多的文本行

[color=blue]
有文本a.txt如下:[code]aaaaaaa                                 aaaa bbbbbb ccccccccccc dddd
aa  aaaaaaa bbbbbbbb cccccccccc ddddddddddddd eeeeeee
     aaaaaaaaaaaa bbbbbbbbbbb cccccccccccccccccccc
                aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa aa
                    aaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbb
aaaaaaaaa        ccccccccc bbbbbbbbbbbbb               ddddddddddddddddd[/code]通过批处理查找出文本中字符数(不含空格)最多的行并输出,很明显就是[code]aaaaaaaaa        ccccccccc bbbbbbbbbbbbb               ddddddddddddddddd[/code]要求:
1 不生成临时文件
2 代码简洁,高效,通用性好
3 加分仍以思路为重
[color=green]
----------------------------------------------------------------------------------------------------------------------------------------------------
至目前已有解决方案:见3楼浅默和4楼本人的方案,期待更多方案的出现。
[/color]




[/color]

[[i] 本帖最后由 batman 于 2008-7-27 23:58 编辑 [/i]]

pusofalse 发表于 2008-7-26 02:18

这题还要输出最多的字符在第几行吗?
如果是,包含空行吗?

浅默 发表于 2008-7-26 06:29

@echo off
Setlocal Enabledelayedexpansion
set n=0
for /f "delims=" %%i in (a.txt) do (
      set "ke=%%i"
      set ke1=!ke: =!
      set m=0
      call :1                  
)
echo %wang%
pause
goto :eof
:1
  if not "!ke1:~%m%,1!"=="" set /a m+=1&goto 1
  if %m% gtr %n% set n=%m%&set wang=%ke%
  goto :eof

batman 发表于 2008-7-26 08:19

下面给出本人的方案一:[code]
@echo off
set num=0
for /f "delims=" %%i in (a.txt) do  set "str=%%i"&call,set "str=%%str: =%%"&call :lp "%%i"
call,echo %%_%num%%% %num%个字符
pause>nul&goto :eof
:lp
set "str=%str:~1%"&set /a n+=1
if defined str goto lp
if %n% geq %num% set "num=%n%"&set "_%num%=%~1"
set "n=0"
[/code]

[[i] 本帖最后由 batman 于 2008-7-26 18:25 编辑 [/i]]

pusofalse 发表于 2008-7-26 16:43

[quote]原帖由 [i]batman[/i] 于 2008-7-26 08:19 发表 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=5936&ptid=1249][img]http://bbs.bathome.net/images/common/back.gif[/img][/url]
下面给出本人的方案一:@echo off
set num=0
for /f "delims=" %%i in (a.txt) do  set "str=%%i"&call,set "str=%%str: =%%"&call :lp "%%i"
call,echo %%_%num%%% %num%个字符
pause>nul&goto :eof
:lp
set  ... [/quote]

有一点小错误,加完分才看出来。变量%%_%num%%是字符数最多的行,没有进行条件约束而每次都赋值给它,导致输出的字符数正确,行的内容却每次都是最后的那一行。

batman 发表于 2008-7-26 18:26

确实如此,粗心了,谢谢兄弟指出。

namejm 发表于 2008-7-30 10:59

  可以考虑 findstr /o 方案。
[quote]/O        在每个匹配行前打印字符偏移量。
[/quote]

batman 发表于 2008-7-31 13:09

[quote]原帖由 [i]namejm[/i] 于 2008-7-30 10:59 发表 [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=6111&ptid=1249][img]http://bbs.bathome.net/images/common/back.gif[/img][/url]
  可以考虑 findstr /o 方案。
[/quote]
思路是对的,但实施起来还真的有点麻烦:
[code]
@echo off&setlocal enabledelayedexpansion
set "max=0"
for /f "delims=" %%i in (1.txt) do set "str=%%i"&echo !str: =!>>2.txt
echo end>>2.txt
for /f "tokens=1* delims=: " %%i in ('findstr /o .* 2.txt') do (
    set /a n+=1&set "_!n!=%%i"&set ".!n!=%%j"
    set /a m=n-1
  if !m! geq 1 call :lp
)
echo.&echo 字符数最多的行为:!a%max%!
echo.&echo     字符数为:%max%个   
del /q 2.txt&pause>nul&goto :eof
:lp
set /a num=_%n%-_%m%-2
if %num% gtr %max% set "max=%num%"&set "a!max!=!.%m%!"
[/code]

keen 发表于 2009-4-17 23:28

太复杂了,想办法优化:[code]@echo off&setlocal enabledelayedexpansion
for /f "delims=" %%i in (a.txt) do (
    set v=%%i
    set n=0
    set /a m+=1
    call :lp
    set _!m!=!n!#%%i
)
set max=0
call :next
for /f "tokens=1 delims=#" %%a in ("!var!") do (
        set s=%%a
        if !s! gtr %max% set max=!s!
)
call :next
for /f "tokens=1,2 delims=#" %%a in ("!var!") do (
        if %%a==%max% echo %%b
)
pause&exit/b

:next
for /l %%i in (1 1 %m%) do (
    set c=%%i
    call,set var=%%_!c!%%
)
goto :eof

:lp
    if "!v:~0,1!" neq " " set /a n+=1
    set v=!v:~1!
    if "!v!" neq "" goto lp
goto :eof[/code]

batman 发表于 2009-4-17 23:41

回复楼上:
兄弟看来是特有求知欲和动手欲的,这么老的练习贴子也让你翻到了,还做了解答,呵呵,加油。。。

keen 发表于 2009-4-17 23:52

只能优化成3楼的了:[code]@echo off&setlocal enabledelayedexpansion
for /f "delims=" %%i in (a.txt) do (
    set v=%%i
    set /a max=0,n=0,m+=1
    call :lp
    if !n! gtr !max! set max=!n!&set xx=%%i
)
echo %xx%&pause&exit/b

:lp
    if "!v:~0,1!" neq " " set /a n+=1
    set v=!v:~1!
    if "!v!" neq "" goto lp
goto :eof[/code]

keen 发表于 2009-4-17 23:55

回复 10楼 的帖子

我是新手,刚从新手训练营出来。
我从新手训练营出来后,就迷失方向了,不知道怎么继续学批处理了,我把论坛挨个翻了一遍,又发现在这一块,有好多题。
我就继续练下去。
请batman多多指教!

batman 发表于 2009-4-18 00:04

呵呵,这块确实是很多人没有注意到的,如果说学批处理有速成班的话,这块就算是的吧。。。
祝兄弟学得开心并学有所成,本人将尽一切能力帮助学习批处理的新人们。

everest79 发表于 2009-4-18 01:35

[code]@echo off
for /f "delims=" %%a in (1.txt) do (
for %%b in (%%a) do call :xxx "%%b"
call :yyy
)
for /f "tokens=2,3 delims=_=" %%i in ('set l_^|sort /+4') do set msg=最长的行为:%%i  长度为:%%j
echo %msg%
pause
goto :eof


:xxx
set "lines=%lines%%~1"
goto :eof

:yyy
set /a x+=1
for /f "skip=1 delims=:" %%i in ('^(echo "%lines%"^&echo.^)^|findstr /o ".*"') do set/a l_%x%=%%i-5
set lines=
goto :eof[/code]只处理这个文本

tireless 发表于 2009-4-18 02:06

[code]@echo off
set max=0
for /f "tokens=*" %%a in (a.txt) do (
  if "%%a" neq "" call :ready "%%a"
)
echo 第 %strline% 行字数最多,共 %max% 个字符:&echo.&echo %str%&echo.
pause & exit /b

:ready
set var=%~1
set var=%var: =%
set /a n=0,line+=1

:lp
set fifteen=%var:~,15%fedcba987654321
set /a n+=0x%fifteen:~15,1%
if "%var:~15%" neq "" set "var=%var:~15%" & goto lp
if %n% gtr %max% set /a "max=n,strline=line" & set str=%~1[/code]

batman 发表于 2009-4-18 02:40

如果文本行都在80个字符以内的话,可用以下这种高效的办法:[code]@echo off&setlocal enabledelayedexpansion
for /l %%a in (1,1,80) do set "k=!k!0"
for /f "delims=" %%a in (a.txt) do (
    set "str=%%a"&set "str=%k% !str: =!"
    set "str=!str:~-80!"&set /a n+=1
    for /f %%b in ("!str!") do set "_%%b=%%a"&set ".%%a=!n!"
)
for /f "tokens=1,2 delims==_" %%a in ('set _') do (
    set "str=%%a"&set "var=%%b"
    for /l %%i in (1,1,80) do if "!str:~%%i!" equ "" set /a num=79-%%i&goto next
)
:next
echo 字符数是多的是!.%var%!行,共有%num%个字符。
pause>nul[/code]

随风 发表于 2009-4-18 15:44

折半法 可处理每行最多 8189 个字符,效率不错。
[code]@echo off&setlocal enabledelayedexpansion
::@随风 @bbs.bathome.net @2009-04-18
for /f "delims=" %%a in (a.txt) do (
   set /a sun+=1,n=8189*2,max=1
   set s=%%a&set s=!s: =!&set "var="
   for /l %%a in (1 1 14) do (
      if defined var set /a n=var
      set /a n/=2
      for %%i in (!n!) do (
         if "!s:~%%i,1!"=="" (set /a var=n) else (
            set s=!s:~%%i!&set /a max+=%%i,var-=%%i
   )))  
   if !h! lss !max! set /a num=sun,h=max
)
echo 字符数最多的行为:!num!
echo 字符数为:!h!
pause[/code]

[[i] 本帖最后由 随风 于 2009-4-18 17:01 编辑 [/i]]

netbenton 发表于 2009-4-30 00:16

取长补短为提速

[code]
@echo off&setlocal enabledelayedexpansion
set m=0
for /f "delims=" %%a in (a.txt) do (
    set "str=%%a"&set "str=!str: =!0"
    for %%b in (!m!) do (if "!str:~%%b,1!" neq "" call :sub "%%a")
)
echo 字符数是多的是%var%行,共有%m%个字符。
pause
goto :eof

:sub
set/a en=m+40
for /l %%a in (!m!,1,!en!) do (
if "!str:~%%a,1!" equ "" (
  set/a n=%%a-1
  if !m! lss !n! set/a m=!n!,n=0&set "var=%~1"
  goto :eof
)
)
set/a m=en
goto :sub

[/code]

netbenton 发表于 2009-4-30 00:30

请留意中间的输出,此法应该是最高效了吧?
[code]@echo off&setlocal enabledelayedexpansion
set m=1
for /f "delims=" %%a in (a.txt) do (
    set "str=%%a"&set "str=!str: =!0"
    for %%b in (!m!) do (if "!str:~%%b,1!" neq "" call :sub "%%a")
)
set /a m-=1
echo 字符数是多的是%var%行,共有%m%个字符。
pause
goto :eof

:sub
set/a en=m+40
for /l %%a in (!m!,1,!en!) do (
        if "!str:~%%a,1!" equ "" (
                echo 较多: %~1
        set/a m=%%a&set "var=%~1"&goto :eof)
)
set/a m=en
goto :sub[/code]

随风 发表于 2009-4-30 01:33

19楼思路非常巧妙,从少到多,只判断字符数多的行,不判断少的行。省略了很多无用的判断。

qzwqzw 发表于 2010-1-5 10:41

看来从心动到行动
还有很长的距离啊[code]::  GetMaxCharText.cmd - 找出字符最多的文本行
::  qzwqzw - 2010-01-05
::
::  基本构思:
::      将原文本每行尾部加足够的一定数量的空格,在文本右侧形成一个不等齐空格区域
::      原文本行越长,其后的空格区域就越长,然后用sort /+n 仅对空格区域进行排序
::      因为区域内都是空格,则排序的依据就是空格的长度,也就是原行文本的长度
::
@echo off & setlocal EnableDelayedExpansion
set maxLineLen=80
set infile=%~sf0
for /l %%i in (1,1,%maxLineLen%) do set zone= !zone!
:: findstr 会忽略空行和仅含空格的行,但显示行号会跨过这些行,这是我们需要的特性
:: 将行号与行文本分别处理,是因为不对齐行号会影响行文本长度的判断
(for /f "tokens=1* delims=:" %%l in ('findstr /n /r /c:"[^ ]"  %infile%') do (
    set LineNo=         %%l
    set LineNo=!LineNo:~-10!
    set Line=%%m
    set Line=!Line: =!
    echo.!LineNo!:!Line!!zone!
))>%temp%\%~sn0~1.t~1
sort /r /+%maxLineLen% %temp%\%~sn0.t~1 > %temp%\%~sn0.t~2
set/p maxLine=< %temp%\%~sn0.t~2 >nul
::  已找到最多字符的行,以下是提取行号和原行文本,行长度因未要求而忽略
for /f "delims=: " %%i in ("%maxline%") do set maxLineIdx=%%i
::  使用find而不是findstr,是因为find够用够简单
set seekLine=find /v /n "" ^^^<%infile%^^^|find "[%maxLineIdx%]"
echo.字符最多的行第 %maxLineIdx% 行,内容为:
for /f "tokens=1* delims=[]" %%l in ('%seekLine%') do echo.%%m
for %%f in (%temp%\%~sn0.t*) do del %%f[/code]

[[i] 本帖最后由 qzwqzw 于 2010-1-5 10:46 编辑 [/i]]

vincentzhou 发表于 2011-1-2 20:56

[code]@echo off&setlocal enabledelayedexpansion
for /f "delims=" %%a in (a.txt) do (
     set /a t+=1
     set i=%%a
     set y!t!=!i!
     set i=!i: =!
     call:x !i!
     set /a n!t!=!num!
)
for /l %%b in (1 1 !t!) do (
if !n%%b! lss !number! (
set /a n%%b=number
set y%%b=!str!
)
set /a number=n%%b
set str=!y%%b!
cls
echo 字符数最多的行是:!y%%b!共有字符:!n%%b! 个
)
pause>nul&exit
:x
    set i=%1
    set num=
:loop
     set i=!i:~1!
     set /a num+=1
     if "!i!"==""  ( echo abc >nul
     )  else ( goto:loop)[/code]

[[i] 本帖最后由 vincentzhou 于 2011-1-9 13:27 编辑 [/i]]

CrLf 发表于 2011-5-15 10:29

[i=s] 本帖最后由 zm900612 于 2011-5-16 13:28 编辑 [/i]

来一个以sort为核心的全新思路:[code]@echo off&setlocal enabledelayedexpansion
set /a $=11,n=4096
for /l %%a in (-2 1) do set $=!$!!$!!$!!$!
(copy a.txt %$%$&findstr .* %$%?>$
for %%a in (2048 1024 512 256 128 64 32 16 8 4 2 1) do sort /+100 /rec !n! $&&set /a n-=%%a,h=n-130||set /a n+=%%a)>nul 2>nul
for /f "delims=" %%a in ('sort /+%h% a.txt') do set long=%%a
echo !long!
del /f /q *$&pause[/code]看到这题又浮了上来,想起正好能用上前阵子开发的用sort命令判断文件中最长行字符数的代码,于是把代码改造了下...
这种以外部命令为主的算法计算小文件时不快,但是在计算大文件时将有速度优势。

qzwqzw 发表于 2011-5-15 21:21

[b] [url=http://www.bathome.net/redirect.php?goto=findpost&pid=78695&ptid=1249]23#[/url] [i]zm900612[/i] [/b]
代码过于求奇求异
又缺乏必要的注释
是在考验读者的耐心
我不欣赏这样的风格

CrLf 发表于 2011-5-16 13:26

[i=s] 本帖最后由 zm900612 于 2011-5-16 13:37 编辑 [/i]

[quote] 23# zm900612
代码过于求奇求异
又缺乏必要的注释
是在考验读者的耐心
我不欣赏这样的风格
[size=2][color=#999999]qzwqzw 发表于 2011-5-15 21:21[/color] [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=78758&ptid=1249][img]http://bbs.bathome.net/images/common/back.gif[/img][/url][/size][/quote]
求奇求异倒不是刻意为之,只是平时喜欢发掘命令的新鲜用法,积累多了,在特定的场合自然会联想到,如果别人不了解我的积累,肯定不容易一下就看明白思路是什么...至于注释,确实没有养成这个代码习惯,以后会注意。

附上对23楼代码的解读:
首先要先说明一下,核心代码来自于前不久研究sort的rec开关时想到的用法:[code]@echo off&setlocal enabledelayedexpansion
set n=32768
(for %%a in (16384 8192 4096 2048 1024 512 256 128 64 32 16 8 4 2 1) do sort /+100 /rec !n! 1.txt&&set /a n-=%%a||set /a n+=%%a)>nul 2>nul
::很像plp兄曾转发过的折半回溯法求字符长度的函数吧?是的,这里就是对那个思路的改造,只不过我是利用sort /rec超过最长行字数时会出错的特性来进行运算的。
echo 最长行有%n%个字符
pause[/code]但是当时这个代码有个缺点,就是当最长行的字数低于128时,sort /rec的值无论设为多少都是不会出错的。
好在曾经拜读过用findstr实现多色显示的代码,从中获取了灵感,于是23楼代码中就用同样的原理为a.txt中每行补齐128个字符的前缀,这样就绕过了原先的限制
23楼代码为了简化而写得比较晦涩,现在翻译成大众版:[code]@echo off&setlocal enabledelayedexpansion
set /a tmp=11,n=4096
::此处的tmp是用于补位的,设成任意两个字符都行,为了压缩代码,把它设成十位数
for /l %%a in (1 1 3) do set tmp=!tmp!!tmp!!tmp!!tmp!
::将tmp补足128位
copy>nul a.txt %tmp:~2%$
::将a.txt保存到一个文件名长度为127个字符的临时文件中
findstr .* %tmp:~2%?>$
::用findstr命令配合通配符实现补位,127个字符长的文件名加上“:”号,刚好128个字符,将结果输出到临时文件中
for %%a in (2048 1024 512 256 128 64 32 16 8 4 2 1) do (
   sort /+100 /rec !n! $&&set /a n-=%%a,h=n-128||set /a n+=%%a
)>nul 2>nul
::用sort /rec判断该临时文件最长行的长度
for /f "delims=" %%a in ('sort /+%h% a.txt') do set long=%%a
::因为知道了最长行的长度,所以可以直接用sort /+n来精确排序,通过for命令,很容易获得最后一行的内容(用set /p也可以,但是考虑到变量长度,还是用for保险,当然用findstr取指定行也是一个不错的选择)。
echo !long!
::不解释
del /f /q *$
::删除临时文件,销赃
pause[/code]

CrLf 发表于 2011-5-16 13:34

23楼的代码中,至少用了三种比较偏门的技巧,所以看起来就显得奇异一些...

qzwqzw 发表于 2011-5-16 15:28

代码看着顺畅多了
不需要实际测试与断点跟踪也能明白思路
给两个建议:
1、set /a h=n-128移到for之外
2、取最长行用for/f+sort又成为线性算法
for/f需要完整遍历整个文件才能取得最长行
与你通篇的算法思路相左
不如仍然用set/p+sort /r
至于变量长度这与for/f中的set long有不同吗?

qzwqzw 发表于 2011-5-16 15:30

另外发现一个小问题
悄悄的告诉你
楼主的题目要求是获取不包含空格的字符数最多行

CrLf 发表于 2011-5-16 16:47

[quote]代码看着顺畅多了
不需要实际测试与断点跟踪也能明白思路
给两个建议:
1、set /a h=n-128移到for之外
2、取最长行用for/f+sort又成为线性算法
for/f需要完整遍历整个文件才能取得最长行
与你通篇的算法思路相 ...
[size=2][color=#999999]qzwqzw 发表于 2011-5-16 15:28[/color] [url=http://bbs.bathome.net/redirect.php?goto=findpost&pid=78844&ptid=1249][img]http://bbs.bathome.net/images/common/back.gif[/img][/url][/size][/quote]

1、这个,如果没记错的话,一个set /a的用时好像是set /a内部二十几个算式的计算耗时,而此处实际上只需要计算12次,所以我感觉这个算式还是能联用就联用
2、最初确实忽略了思路的连贯性,刚刚解释代码的时候也想到这一点,所以加了句用“findstr也是不错的选择”
关于变量长度,set /p与set的区别在于set "str=#@#¥#……&"时变量长度上限8192字节,而set /p str=请输入#@#¥#……&时则只能定义1024字节,这与前面折半回溯所支持的字符长度相悖,所以放弃了set /p。
改进的思路是:[code]endlocal
for /f "tokens=1* delims=:" %%a in ('findstr /n a.txt^|findstr 1:') do set "long=%%b"
...[/code]3、没看到楼主要求是不含空格,不过好像在谁的代码中看到了"findstr /o"...
我的代码习惯可能比较明显,要么大量使用外部命令,要么一堆for嵌套+set,减少call和goto。如果碰到大文件,外部命令优势明显,但是若要进行更精细的筛选,那还是只好老老实实用for了...

qzwqzw 发表于 2011-5-16 20:11

set /a的问题不是在效率上
而是在逻辑上
因为你在set /a n+=%%a时并没有计算h
那么如果set /a n-=%%a在特定条件下没有运行一次
那么h的取值为空
或者set /a n-=%%a最后一次没有运行
那么h的取值会差1

set/p的字符长度限制确实是忘记了
记忆力确实越来越差了
扩展测试了一下
windows记事本也有每行1024字符的限制
find命令同样也有

不过你的改进思路有些问题
似乎忘记了sort /r

页: [1] 2

Powered by Discuz! Archiver 7.2  © 2001-2009 Comsenz Inc.