从批处理文件中的单词列表中搜索特定单词

searching a specific word from a list of words in batch file

我有一个包含单词的 %list% 变量(参见代码),我想搜索一个以字母 'Y' 结尾(忽略区分大小写)并以用户输入的最后一个字母。用户输入可以是任何有效的词。一旦我找到那个特定的词,我就想把它存储到一个变量中(%myWord%)。我的意图是稍后在代码中使用 %myWord% 变量。如果我已经使用了 %list% 中的那个词,那么我想向用户显示一个新词。我的单词列表以任意字母开头,以 'Y' 字母结尾,并按字母顺序排列。 %list% 很长。 以下是我的代码。我觉得自己迷路了,找不到解决这个问题的方法。

@echo off

:START
set /p INPUTWORD=>Enter a word:
SET lastletter=%INPUTWORD:~-1%

set "list=daddy day gladly happily key pay randomly ray say urgency utility yesterday"
For %%L in (%list%) do (
    SET endingY=%%L:~-1%
 For %%X in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do (
  if findstr /i "%lastletter%" %%X EQU first letter of the words in %list%
   if %%L:~-1% EQU "y"
    if %myWord% is already shown to user then search for a new word.
     and then set myWord==%%L  (new word; unused one)

echo My Word is: %myWord%
pause
goto START

例如:

第 1 节课: 输入一个词:狗

我的话是:乐意

第 2 节: 输入一个词:牦牛

我的话是:关键

使用任何类型的函数都可以。只要它搜索正确的词。 这似乎是可能的,但我已经厌倦了思考。我想我可能需要新鲜了。

您的描述有误。它说:"I want to search a word which ends with letter 'Y' and which starts with the first letter of user's input",但代码查找以用户输入的 last 字母开头的单词。我下面的解决方案使用代码规范。

@echo off
setlocal EnableDelayedExpansion

set "list=daddy day gladly happily key pay randomly ray say urgency utility yesterday"

:START
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD goto :EOF

rem Get last letter of user's input
SET lastLetter=%INPUTWORD:~-1%

rem For each word in the list...
For %%L in (%list%) do (

   rem Store the word in "myWord" variable
   set "myWord=%%L"

   rem If the word starts with the last letter of user's input
   rem and ends with letter 'Y' (ignoring the case-sensitive)
   if /I "!myWord:~0,1!!myWord:~-1!" equ "%lastLetter%Y" (

      rem Remove the already used word from the list
      rem so the next time, a new word be shown to user
      set "list=!list:%%L=!"

      rem And break the FOR loop, because the word was found
      goto break

   )

)

rem If all words in the list was reviewed, indicate that the word was not found
set "myWord=Word not found in list"
:break

echo My Word is: %myWord%
pause
goto START

对这个问题的一些观察:

  • 如果所有单词都以'Y'字母结尾,则不需要检查这一点("that the word ends in 'Y'")。
  • 单词按字母顺序排列没有任何优势。无论如何,需要审查整个列表以搜索目标词,除非开发出更复杂的方法。

代码注释中包含说明。

这可能看起来代码很多,但实际上效率很高。

无需验证找到的以 y 结尾的单词,因为您的列表仅包含以 y 结尾的单词。

此解决方案假设列表中的所有单词至少有两个字母。

@echo off
setlocal EnableDelayedExpansion

:: The list must start and end with a space, the word order does not matter
set "list= daddy day gladly happily key pay randomly ray say urgency utility yesterday "

:start
echo(

:: Exit if list is empty (nothing but spaces)
if "%list: =%" equ "" (
  echo No more words in list
  exit /b
)

:: Get the users input, exit if none
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD exit /b

:: Look for the first word that begins with the last letter of the input
:: and delete everything up through the first letter of the matching word
set "test=!list:* %INPUTWORD:~-1%=!"

:: If the modified list matches the original, then no word was found.
:: Give message and try again
if "%test%" equ "%list%" (
  echo myWord not found
  goto :start
)

:: Get the remainder of the word and rebuild the complete word
for %%W in (%test%) do (
  set "myWord=%INPUTWORD:~-1%%%W"
  goto :break
)
:break

:: Remove the word from the list
set "list=!list: %myWord% = !"

:: Show the result and loop back
echo myWord=%myWord%
goto :start

这个解决方案应该是最有效的方法,因为它根据单词的第一个字母将原始列表拆分为子列表。这样,通过 if defined subList[%lastLetter%] 可以立即测试某个单词是否存在,并且单词的提取是在一个更短的变量中进行的。另外,这种方法很清楚,因为代码的大小。

@echo off
setlocal EnableDelayedExpansion

set "list=daddy day gladly happily key pay randomly ray say urgency utility yesterday"

rem Split the list in sub-lists (an array) based on the first letter of the words
for %%w in (%list%) do (
   set "word=%%w"
   for /F %%l in ("!word:~0,1!") do set "subList[%%l]=!subList[%%l]! %%w"
)

ECHO CONTENTS OF THE SUB-LISTS:
SET SUBLIST
ECHO/

:START
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD goto :EOF

rem Get last letter of user's input
SET lastLetter=%INPUTWORD:~-1%

rem If a sub-list for that letter exists...
if defined subList[%lastLetter%] (

   rem Extract the first word from such sub-list
   for /F "tokens=1*" %%a in ("!subList[%lastLetter%]!") do (
      set "myWord=%%a"
      set "subList[%lastLetter%]=%%b"
   )

) else (
   set "myWord=Word not found in list"
)

echo My Word is: %myWord%
echo/
goto START

示例会话:

CONTENTS OF THE SUB-LISTS:
subList[d]= daddy day
subList[g]= gladly
subList[h]= happily
subList[k]= key
subList[p]= pay
subList[r]= randomly ray
subList[s]= say
subList[u]= urgency utility
subList[y]= yesterday

>Enter a word: dog
My Word is: gladly

>Enter a word: yak
My Word is: key

>Enter a word: dad
My Word is: daddy

>Enter a word: dad
My Word is: day

>Enter a word: dad
My Word is: Word not found in list

>Enter a word:

有关批处理中数组的进一步说明,请参阅this post

编辑:我试图在我的解决方案与 dbenham 的解决方案之间进行计时测试,但我无法完成!!!

@echo off
setlocal EnableDelayedExpansion

set "list0=daddy day gladly happily key pay randomly ray say urgency utility yesterday"

rem Create the initial list repeating the previous "list0" 100 times (= 1200 words)
rem changing the initial letter by a random one

echo Creating initial list...
set "letter=abcdefghijklmnopqrstuvwxyz"
set "list="
for /L %%t in (1,1,100) do (
   for %%a in (%list0%) do (
      set "word=%%a"
      set /A i=!random! %% 26
      for /F %%i in ("!i!") do set "list=!list! !letter:~%%i,1!!word:~1!"
   )
)


rem ========================
echo Testing Aacini's

rem Start Aacini's code test
for /F "tokens=1-3 delims=:." %%a in ("%time%") do (
   set /A "H=%%a, M=1%%b%%100, S=1%%c%%100, startA=(H*60+M)*60+S"
)

rem Split the list in sub-lists (an array) based on the first letter of the words
for %%w in (%list%) do (
   set "word=%%w"
   for /F %%l in ("!word:~0,1!") do set "subList[%%l]=!subList[%%l]! %%w"
)

rem Get 1000 words selecting a random last letter 

for /L %%? in (1,1,1000) do (

set /A i=!random! %% 26
for /F %%i in ("!i!") do SET "lastLetter=!letter:~%%i,1!"

rem If a sub-list for that letter exists...
if defined subList[!lastLetter!] (

   rem Extract the first word from such sub-list
   for /F %%L in ("!lastLetter!") do for /F "tokens=1*" %%a in ("!subList[%%L]!") do (
      set "myWord=%%a"
      set "subList[%%L]=%%b"
   )

) else (
   set "myWord=Word not found in list"
)

echo %%?- My Word is: !myWord!
REM echo/
REM goto START
)

rem End Aacini's code test
for /F "tokens=1-3 delims=:." %%a in ("%time%") do (
   set /A "H=%%a, M=1%%b%%100, S=1%%c%%100, endA=(H*60+M)*60+S"
)


rem ========================
echo Testing dbenham's


rem Start dbenham's code test

:: The list must start with dot space and end with space dot, the word order does not matter
set "list=. %list% ."

for /F "tokens=1-3 delims=:." %%a in ("%time%") do (
   set /A "H=%%a, M=1%%b%%100, S=1%%c%%100, startD=(H*60+M)*60+S"
)

rem Get 1000 words selecting a random last letter 

for /L %%? in (1,1,1000) do (

set /A i=!random! %% 26
for /F %%i in ("!i!") do SET "LastLetter=!letter:~%%i,1!"

REM :start
REM echo(

Rem Exit if list is empty
if "!list: =!" equ ".." (
  echo No more words in list
  goto endDbenham
)

Rem Iterate value of list after replacing "<space>%LastLetter%" with "<newLine>%LastLetter%".
Rem Skip the first line and take the first token, which will be myWord.
Rem If no substitution, then only one line so DO will not fire.
Rem The empty line below (after the FOR line^) is critical - DO NO REMOVE
set "break="
for /F %%L in ("!LastLetter!") do for /f "usebackq skip=1" %%W in ('!list: %%L^=^

%%L!') do if not defined break (                    

  REM remove myWord from the list
  set "list=!list: %%W = !"

  REM show result and loop back for more (breaks out of loop^)
  echo %%?- myWord = %%W
  REM goto :start
  set "break=Yes"
)

if not defined break (
Rem Only reaches here if myWord not found
echo %%?- myWord not found
REM goto :start
)

)


rem =======================

:endDbenham

rem End dbenham's code test
for /F "tokens=1-3 delims=:." %%a in ("%time%") do (
   set /A "H=%%a, M=1%%b%%100, S=1%%c%%100, endD=(H*60+M)*60+S"
)

set /A elapsedA=endA-startA, elapsedD=endD-startD
echo/
echo Aacini's:  %elapsedA% seconds
echo dbenham's: %elapsedD% seconds

我相信这是本机批处理的最终优化解决方案 - 甚至比 Aacini 的第二个解决方案更快。

没有任何预处理,只是将点 space 放在列表的前面,space 点放在列表的末尾。

它使用续行(转义行尾)在所需单词前插入换行符。

@echo off
setlocal EnableDelayedExpansion

:: The list must start with dot space and end with space dot, the word order does not matter
set "list=. daddy day gladly happily key pay randomly ray say urgency utility yesterday ."

:start
echo(

:: Exit if list is empty
if "%list: =%" equ ".." (
  echo No more words in list
  exit /b
)

:: Get the user's input, exit if none
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD exit /b

:: Get the last letter of input
set "LastLetter=%INPUTWORD:~-1%"

:: Iterate value of list after replacing "<space>%LastLetter%" with "<newLine>%LastLetter%".
:: Skip the first line and take the first token, which will be myWord.
:: If no substitution, then only one line so DO will not fire.
:: The empty line below (after the FOR line) is critical - DO NO REMOVE
for /f "usebackq skip=1" %%W in ('!list: %LastLetter%^=^

%LastLetter%!') do (                    

  REM remove myWord from the list
  set "list=!list: %%W = !"

  REM show result and loop back for more (breaks out of loop)
  echo myWord = %%W
  goto :start
)

:: Only reaches here if myWord not found
echo myWord not found
goto :start



更新

受 Aacini 的启发,我对我们的优化方法做了一些计时测试。我们真的在谈论分裂头发:-)

最终结果如下:

                       dbenham      Aacini
                     -----------  -----------
Preprocess the list:   2.00 msec  104.00 msec
One input & lookup:    2.91 msec    2.82 msec

唯一显着的区别在于预处理,但就用户体验而言,即便如此也可能毫无意义。

以下是我的测试方法:

我删除了所有注释并稍微调整了代码结构,以确保两者完成相同的工作量并给出相同的结果。

我编制了一个包含 728 个单词的列表(不再以 "y" 结尾,但这并不重要)。我还编写了一个包含 551 个单词的输入文件,每行一个,包括最后一个由句点组成的 "word",以便它执行 "word not found" 分支。

我保存两个脚本的 "my word" 输出并进行比较以显示它们产生相同的结果。

dbenham.bat

@echo off
setlocal EnableDelayedExpansion

:: Define list
set "base=andy bay cay daddy day easy fly gladly happily inky jay key lay may nay ornery pray quay randomly ray say truancy urgency vacancy way xray yesterday zesty "
for %%L in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do set "list=!list! !base: =%%L !"

:: Measure preprocessing time
set t1_0=%time%
set "list=.!list!."
set t1_1=%time%

:: Measure lookup time
set /a cnt=0
set "t2_0=%time%"
:start
echo(
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD goto :done
set /a cnt+=1
set "LastLetter=%INPUTWORD:~-1%"
for /f "usebackq skip=1" %%W in ('!list: %LastLetter%^=^

%LastLetter%!') do (                    
  set "list=!list: %%W = !"
  echo My word = %%W
  goto start
)
echo My word not found in list
goto start

:done
set "t2_1=%time%"
call :elapsed t1_0 t1_1 t1
call :elapsed t2_0 t2_1 t2
>&2 echo Preprocessing time for 728 words = %t1%0 msec
>&2 echo Lookup time for %cnt% inputs = %t2%0 msec
exit /b 

:elapsed  t1  t2  rtn
setlocal
for /f "tokens=1-8 delims=:., " %%a in ("!%~1: =0! !%~2: =0!") do (
  set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100, t2=(((1%%e*60)+1%%f)*60+1%%g)*100+1%%h-36610100, tDiff=t2-t1"
)
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
endlocal & set "%~3=%tDiff%"
exit /b

Aacini.bat

@echo off
setlocal EnableDelayedExpansion

:: Define list
set "base=andy bay cay daddy day easy fly gladly happily inky jay key lay may nay ornery pray quay randomly ray say truancy urgency vacancy way xray yesterday zesty "
for %%L in (a b c d e f g h i j k l m n o p q r s t u v w x y z) do set "list=!list! !base: =%%L !"

:: Measure preprocessing time
set "t1_0=%time%"
for %%w in (%list%) do (
  set "word=%%w"
  for /F %%l in ("!word:~0,1!") do set "subList[%%l]=!subList[%%l]! %%w"
)
set "t1_1=%time%"

:: Measure lookup time
set /a cnt=0
set "t2_0=%time%"
:START
echo(
set "INPUTWORD="
set /p "INPUTWORD=>Enter a word: "
if not defined INPUTWORD goto :done
set /a cnt+=1
set lastLetter=%INPUTWORD:~-1%
if defined subList[%lastLetter%] (
  for /F "tokens=1*" %%a in ("!subList[%lastLetter%]!") do (
    echo My word = %%a
    set "subList[%lastLetter%]=%%b"
  )
) else (
  echo My word not found in list
)
goto START

:done
set "t2_1=%time%"
call :elapsed t1_0 t1_1 t1
call :elapsed t2_0 t2_1 t2
>&2 echo Preprocessing time for 728 words = %t1%0 msec
>&2 echo Lookup time for %cnt% inputs = %t2%0 msec
exit /b 

:elapsed  t1  t2  rtn
setlocal
for /f "tokens=1-8 delims=:., " %%a in ("!%~1: =0! !%~2: =0!") do (
  set /a "t1=(((1%%a*60)+1%%b)*60+1%%c)*100+1%%d-36610100, t2=(((1%%e*60)+1%%f)*60+1%%g)*100+1%%h-36610100, tDiff=t2-t1"
)
if !tDiff! lss 0 set /a tDiff+=24*60*60*100
endlocal & set "%~3=%tDiff%"
exit /b

-- 测试运行 结果--

D:\test>dbenham <test.txt >dbenham.txt
Preprocessing time for 728 words = 10 msec
Lookup time for 551 inputs = 1610 msec

D:\test>dbenham <test.txt >dbenham.txt
Preprocessing time for 728 words = 00 msec
Lookup time for 551 inputs = 1600 msec

D:\test>dbenham <test.txt >dbenham.txt
Preprocessing time for 728 words = 00 msec
Lookup time for 551 inputs = 1610 msec

D:\test>dbenham <test.txt >dbenham.txt
Preprocessing time for 728 words = 00 msec
Lookup time for 551 inputs = 1590 msec

D:\test>dbenham <test.txt >dbenham.txt
Preprocessing time for 728 words = 00 msec
Lookup time for 551 inputs = 1620 msec

D:\test>aacini <test.txt >aacini.txt
Preprocessing time for 728 words = 100 msec
Lookup time for 551 inputs = 1600 msec

D:\test>aacini <test.txt >aacini.txt
Preprocessing time for 728 words = 110 msec
Lookup time for 551 inputs = 1540 msec

D:\test>aacini <test.txt >aacini.txt
Preprocessing time for 728 words = 90 msec
Lookup time for 551 inputs = 1560 msec

D:\test>aacini <test.txt >aacini.txt
Preprocessing time for 728 words = 110 msec
Lookup time for 551 inputs = 1580 msec

D:\test>aacini <test.txt >aacini.txt
Preprocessing time for 728 words = 110 msec
Lookup time for 551 inputs = 1580 msec

D:\test>fc dbenham.txt aacini.txt
Comparing files dbenham.txt and AACINI.TXT
FC: no differences encountered

我将每个脚本的 5 次计时取平均值,并将查找时间除以 551 得到一次迭代的时间。