0

I have a %list% variable which has words (see the code) and I want to search a word which ends with letter 'Y' (ignoring the case-sensitive) and which starts with the last letter of user's input. User input would be any valid word. Once I find that specific word then I want to store it into a variable (%myWord%). My intention is to use the %myWord% variable later in the code. If I already used that word from the %list% then I want a new word shown to user. My list of words starts with any letter and end in 'Y' letter and are in alphabetic order. The %list% is pretty long. Following is my code. I feels like I am lost and I couldn't find the solution to this issue.

@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

For example:

Session 1: Enter a word: dog

My word is: gladly

Session 2: Enter a word: yak

My word is: key

Using any type of function is totally fine. As long as it search for the right word. This seems possible but I'm tired of thinking. I think I might need to be fresh.

4 Answers4

1

Your description is wrong. It says: "I want to search a word which ends with letter 'Y' and which starts with the first letter of user's input", but the code look for a word which starts with the last letter of user's input. My solution below use the code specification.

@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

A couple observations on this problem:

  • If all the words ends in 'Y' letter, then it is not necessary to check this point ("that the word ends in 'Y'").
  • It has not any advantage that the words be in alphabetic order. Anyway the whole list needs to be reviewed searching for the target word, unless a much more complicated method be developed.
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • this works fine, but when it comes to handle large list it took about 5.20 seconds to do the search. But still works. The below solution with array, does searching and sorting first and then it makes faster later. Thanks. – Chhandra Aadhikary Jun 22 '15 at 06:47
1

Explanation included in code comments.

This may look like a lot of code, but it is actually quite efficient.

There is no need to verify the found word ends with y since your list only contains words that end with y.

This solution assumes all words in the list have at least two letters.

@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
dbenham
  • 127,446
  • 28
  • 251
  • 390
1

This solution should be the most efficient method, because it split the original list in sub-lists accordingly to the first letter of the words. This way, the testing if a certain word exists is immediate via a if defined subList[%lastLetter%] and the extraction of the word is made in a much shorter variable. Also, this method is clear because the size of the code.

@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

An example session:

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:

For a further description of arrays in Batch, see this post.

EDIT: I tried to implement a timing test between my solution vs. dbenham's, but I couldn't completed it!!!

@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
Community
  • 1
  • 1
Aacini
  • 65,180
  • 12
  • 72
  • 108
  • Great it load for a while to do searching and sorting, but it makes easy later when displaying. This is another solution that I liked. The previous solution also works. Thanks. – Chhandra Aadhikary Jun 22 '15 at 06:49
  • I think I may have one upped you ;-) see [my 2nd answer](http://stackoverflow.com/a/30980052/1012053) – dbenham Jun 22 '15 at 12:32
  • See my updated 2nd answer with timings and methodoloy - not much difference :-) – dbenham Jun 22 '15 at 19:00
0

I believe this is the ultimate optimized solution for native batch - even faster than Aacini's 2nd solution.

There is no preprocessing, other than to put dot space at the front of the list, and space dot at the end.

It uses line continuation (escaping the end-of-line) to insert a newline character before the desired word.

@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



UPDATE

Inspired by Aacini, I did some timing tests of our optimised methods. We are really talking about splitting hairs :-)

Here are the final results:

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

The only significant difference is in the preprocessing, but even that is probably meaningless as far as user experience.

Below is my testing methodology:

I removed all comments and restructured the code a bit to make sure both do the same amount of work and give the same result.

I rigged up a list of 728 words (no longer ending in "y", but that shouldn't really matter). I also rigged up an input file with 551 words, one per line, including a last "word" consisting of a period so that it excercises the "word not found" branch.

I save the "my word" output of both scripts and compare to show they produce identical results.

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

-- Test run results --

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

I averaged the 5 timings for each script, and divided the lookup time by 551 to get the time for one iteration.

dbenham
  • 127,446
  • 28
  • 251
  • 390