Sorting input numbers in batch file - sorting

Im trying to create a program in which you will enter 5 numbers randomly and will automatically shows the sorted entered numbers from lowest to highest. Here is my code.
#echo off
:main
set /p num1="Enter 1st :"
set /p num2="Enter 2nd :"
set /p num3="Enter 3rd :"
set /p num4="Enter 4th :"
set /p num5="Enter 5th :"
set /a high=0
set /a low=0
set /a ave=(num1+num2+num3+num4+num5)/5
if %num1% GTR %num2% (set /a high=%num1%) ELSE set /a high =%num2%
if %num3% GTR %high% (set /a high=%num3%)
if %num4% GTR %high% (set /a high=%num4%)
if %num5% GTR %high% (set /a high=%num5%)
if %num1% LSS %num2% (set /a low=%num1%) ELSE set /a low =%num2%
if %num3% LSS %low% (set /a low=%num3%)
if %num4% LSS %low% (set /a low=%num4%)
if %num5% LSS %low% (set /a low=%num5%)
ECHO.
ECHO Average: %ave%
ECHO Highest: %high%
ECHO Lowest: %low%
ECHO Input Numbers: %num1% %num2% %num3% %num4% %num5%
:end
set /p return="Continue?"
if "%return%"=="yes" GOTO main
GOTO exit
:exit

There are several ways to solve this problem. In the "one by one" method each variable must be compared vs. another one; this method is too much complex and is the one you used to read the values. You may also use sort command to sort the numbers (as Rafael tried in his answer); however, the numbers must be padded at left side with zeros in order to be correctly sorted.
When a certain process is repeated over several elements, the usual way to solve such a problem is via an array. You may find descriptions about array concept in several sites, like in Wikipedia. You may read how to use arrays in a Batch file at this post.
The Batch file below make good use of the fact that environment variables are kept automatically sorted; this way, the program just insert the numbers in an array and then take out its elements, so the sort process itself is not required:
#echo off
setlocal EnableDelayedExpansion
set N=5
:main
rem Delete array elements from previous cycle, if any:
for /F "delims==" %%a in ('set array[ 2^>NUL') do set "%%a="
rem Read the numbers; at same time, accumulate they in "ave" variable
rem and create the array with the proper index (with left zeros)
set ave=0
for /L %%i in (1,1,%N%) do (
set /P num="Enter number %%i :"
set /A ave+=num
set index=000000000!num!
set array[!index:~-10!]=!num!
)
set /A ave=ave/N
rem Assemble the output line and get low and high values
set "output="
set "low="
for /F "tokens=2 delims==" %%a in ('set array[') do (
if not defined low set low=%%a
set high=%%a
set output=!output! %%a
)
ECHO/
ECHO Average: %ave%
ECHO Highest: %high%
ECHO Lowest: %low%
ECHO Input Numbers: %output%
set /p return="Continue?"
if "%return%"=="yes" GOTO main

I was looking for an answer to a similar "sort numbers" question, and want to add example of using SORT command as an alternative for completeness. Note that using SORT requires writing a file to disk and seems less efficient:
#echo off
setlocal EnableDelayedExpansion
set "num_1=2" & set "num_2=5" & set "num_3=1" & set "num_4=18" & set "num_5=10"
for /f "tokens=2 delims==" %%i in ('set num_') do (set /a "var=1000000+%%i"
echo !var! >> nums.txt)
for /f %%j in ('sort nums.txt') do (set /a "var=%%j-1000000"
set "nums=!nums! !var!")
del /q nums.txt & echo !nums!
:eol

Related

Aligning output from batch file For loop

REM ************************ HIGH SCORES TABLE
**********************************************
:highscorestable
set /a count = 0
for /f "tokens=1,2,3 delims=-" %%i in (highscores.txt) do (
set hs=%%i
set hsn=%%j
set hsv=%%k
set hst=%%jscored %%iusing%%k
set hsn1=!hsn!
set hsv1=!hsv!
set hs1=!hs!
set hsn1= %hsn1%
set hsv1= %hsv1%
set hs1= %hs1%
echo %hsn1:~-15% %hsv1:~-15% %hs1:~-15%
set /a count+=1
if "!count!"=="5" goto :end
)
:end
echo.
pause
I'm pulling the first 5 lines from a text file using a For loop. My variables populate fine, however I'm struggling with the required alignment.
My ultimate end result should be:
James Commitment 300
Markos Excellence 290
Jeremy Si Party 50
What obvious thing am I missing here?
You could try this:
SetLocal EnableDelayedExpansion
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
Set "count=0"
For /F "UseBackQTokens=1-3Delims=-" %%i In ("highscores.txt") Do (
Set "hs=%%i"
Set "hsn=%%j"
Set "hsv=%%k"
Set "hst=%%jscored %%iusing%%k"
Set "hs= %%i "
Set "hsn1=%%j "
Set "hsv1=%%k "
Echo !hsn1:~,15!!hsv1:~,15!!hs:~-15!
Set/A count+=1
If "!count!"=="5" GoTo :end
)
:end
Echo(
Pause
Or without the possibly unnecessary variables:
SetLocal EnableDelayedExpansion
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
Set "count=0"
For /F "UseBackQTokens=1-3Delims=-" %%i In ("highscores.txt") Do (
Set "hs= %%i "
Set "hsn=%%j "
Set "hsv=%%k "
Set "hst=%%jscored %%iusing%%k"
Echo !hsn:~,15!!hsv:~,15!!hs:~-15!
Set/A count+=1
If "!count!"=="5" GoTo :end
)
:end
Echo(
Pause
In both cases, I've added the necessary SetLocal EnableDelayedExpansion line just in case it isn't in your script prior to your provided code.
Edit
You can also alter the code a little forego delayed expansion: (my preferred option)
REM **************************** HIGH SCORES TABLE ****************************
:highscorestable
For /F "Tokens=1-4Delims=:-" %%A In ('FindStr/N $ "highscores.txt"'
) Do If %%A LEq 5 (Set "hst=%%Cscored %%Busing%%D"
Set "hss= %%B"
Set "hsn=%%C "
Set "hsv=%%D "
Call Echo %%hsn:~,15%%%%hsv:~,15%%%%hss:~-10%%)
Echo(
Pause
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "filename1=%sourcedir%\q46747991.txt"
REM ************************ HIGH SCORES TABLE
REM **********************************************
:highscorestable
set /a count = 0
SET "manyspaces= "
for /f "tokens=1,2,3 delims=-" %%i in (%filename1%) do (
set hs=%%k&CALL :align hs -8
set hsn=%%i&CALL :align hsn 15
set hsv=%%j&CALL :align hsv 10
ECHO !hsn!!hsv!!hs!
set /a count+=1
if "!count!"=="5" goto end
)
:end
echo.
GOTO :EOF
:align
IF %2 gtr 0 (
CALL SET "%1=%%%1%%%manyspaces%"
CALL SET "%1=%%%1:~0,%2%%"
) ELSE (
CALL SET "%1=%manyspaces%%%%1%%"
CALL SET "%1=%%%1:~%2%%"
)
GOTO :eof
I edited your results for a source file which I named to suit my system, hence the sequence of coulmns is different from your unpublished source. I changed the metavariable-assignment to suit.
The :align routine peels potatoes by recognising the second argument as the required column-width, positive for left-align and negative for right-align.
The variable manyspaces is set to an obvious value, of sufficient length to cope with the widest column required. Obviously, since it won't change once established, it's best set in the very beginning of the batch.
The routine uses the call set %%var%% method so that it will work regardless of whether delayedexpansion is invoked or not.
The mechanics are, for instance
CALL SET "%1=%%%1%%%manyspaces%"
with %1=fred
First, parse the command. %1 is replaced by fred and %% by %, yielding
set
"fred=
%fred%[spaces]"
So appends the space-string to the current value of the environment variable specified as %1
The second set - analyse similarly; result is assigned to the environment variable specified as %1
So the routine can be used to generate a fixed-width string, appropriately aligned using any ordinary variable, even if the variable has a value of nothing (ie. is undefined)

sorting files according to keywords, need a more database-y solution

I'm making a script that sorts video files into folders by checking for known keywords in the file. As the amount of keywords grows out of control the script becomes very slow, taking several seconds for each file to be processed.
#echo off
cd /d d:\videos\shorts
if /i not "%cd%"=="d:\videos\shorts" echo invalid shorts dir. && exit /b
:: auto detect folder name via anchor file
for /r %%i in (*spirit*science*chakras*) do set conspiracies=%%~dpi
if not exist "%conspiracies%" echo conscpiracies dir missing. && pause && exit /b
for /r %%i in (*modeselektor*evil*) do set musicvideos=%%~dpi
if not exist "%musicvideos%" echo musicvideos dir missing. && pause && exit /b
for %%s in (*) do set "file=%%~nxs" & set "full=%%s" & call :count
for %%v in (*) do echo can't sort "%%~nv"
exit /b
:count
set oldfile="%file%"
set newfile=%oldfile:&=and%
if not %oldfile%==%newfile% ren "%full%" %newfile%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"music" >nul && set words=%words%, music&& set /a count+=1
echo "%~n1" | findstr /i /c:"official video" >nul && set words=%words%, official video&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for music videos
set musicvideoscount=%count%
set count=0
set words= & rem
echo "%~n1" | findstr /i /c:"misinform" >nul && set words=%words%, misinform&& set /a count+=1
echo "%~n1" | findstr /i /c:"antikythera" >nul && set words=%words%, antikythera&& set /a count+=2
set words=%words:has, =has %
set words=%words: , =%
if not %count%==0 echo "%file%" has "%words%" %count%p for conspiracies
set conspiraciescount=%count%
set wanted=3
set winner=none
:loop
:: count points and set winner (in case of tie lowest in this list wins, sort accordingly)
if %conspiraciescount%==%wanted% set winner=%conspiracies%
if %musicvideoscount%==%wanted% set winner=%musicvideos%
set /a wanted+=1
if not %wanted%==15 goto loop
if not "%winner%"=="none" move "%full%" "%winner%" >nul && echo "%winner%%file%" && echo.
Notice the "weight value" for each keyword. It counts the total points for each category, finds the largest value and moves the file to the folder appointed to that category. It also displays the words it has found and lastly lists files it finds unsortable so I can add keywords or tweak weight values.
I have stripped the amount of folders and keywords in this sample to bare minimum. The full script has six folders and 64k size with all the keywords (and growing).
#ECHO OFF
SETLOCAL
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories=music conspiracies"
REM SET "categories=conspiracies music"
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|%%s^|%%t
)
)
)>"%tempfile%"
SET "lastname="
FOR /f "tokens=1,2,*delims=|" %%a IN ('sort "%tempfile%"') DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner="
SET /a maxfound=0
FOR %%v IN (%categories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO CALL :compare %%w %%x
)
IF DEFINED winner ECHO %winner% %lastname:&=and%
:RESET
FOR %%v IN (%categories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
:compare
IF %2 lss %maxfound% GOTO :EOF
IF %2 gtr %maxfound% GOTO setwinner
:: equal scores use categories to determine
IF DEFINED winner GOTO :eof
:Setwinner
SET "winner=%1"
SET maxfound=%2
GOTO :eof
You would need to change the setting of sourcedir to suit your circumstances.
I used a file named q45196316.txt containing this category data for my testing.
music,6,music
music,8,Official video
conspiracies,3,misinform
conspiracies,6,antikythera
missing,0,not appearing in this directory
I believe your problem is that repeatedly executing findstr is time-consuming.
This approach uses a data file containing lines of category,weight,mask. The categories variable contains a list of the categories in order of preference (for when the score is equal)
Read the data file, assigning category to %%s, weight to %%t and mask to %%u and then do a directory-scan using the mask. This will echo a line to the tempfile in the format name|category|weight for each name-match found. dir seems to be very fast after the first scan.
The resultant tempfile will thus have one line for each filename+category plus the weight, so if a filename fits into more than one category, more than one entry will be created.
We then scan a sorted version of that file and resolve the score.
First, if the filename changes, we can report on the last filename. This is done by comparing the values in the variables $categoryname. Since these are scanned in the order %categories% then the first category in the list is chosen if there is an equivalence of scores. The scores are then reset and lastname initialised to the new filename.
We then accumulate the score into $categoryname
So - I believe that will be a bit faster.
Revision
#ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION
SET "sourcedir=U:\sourcedir"
SET "tempfile=%temp%\somename"
SET "categories="rock music" music conspiracies"
REM SET "categories=conspiracies music"
:: set up sorting categories
SET "sortingcategories="
FOR %%a IN (%categories%) DO SET "sortingcategories=!sortingcategories!,%%~a"
SET "sortingcategories=%sortingcategories: =_%"
:: Create "tempfile" containing lines of name|sortingcategory|weight
(
FOR /f "tokens=1,2,*delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\*%%u*" 2^>nul'
) DO (
ECHO %%a^|!sortingcategory!^|%%t^|%%s^|%%u
)
)
)>"%tempfile%"
SET "lastname="
SORT "%tempfile%">"%tempfile%.s"
FOR /f "usebackqtokens=1,2,3delims=|" %%a IN ("%tempfile%.s") DO (
CALL :resolve %%b %%c "%%a"
)
:: and the last entry...
CALL :resolve dummy 0
GOTO :EOF
:: resolve by totalling weights (%2) in sortingcategories (%1)
:: for each name (%3)
:resolve
IF "%~3" equ "%lastname%" GOTO accum
:: report and reset accumulators
IF NOT DEFINED lastname GOTO RESET
SET "winner=none"
SET /a maxfound=0
FOR %%v IN (%sortingcategories%) DO (
FOR /f "tokens=1,2delims=$=" %%w IN ('set $%%v') DO IF %%x gtr !maxfound! (SET "winner=%%v"&SET /a maxfound=%%x)
)
ECHO %winner:_= % %lastname:&=and%
:RESET
FOR %%v IN (%sortingcategories%) DO SET /a $%%v=0
SET "lastname=%~3"
:accum
SET /a $%1+=%2
GOTO :eof
I've added a few significant comments.
You can now have spaces in category names - you need to quote the name (for reporting purposes) within the set catagories... statement.
sortingcategories is automatically derived - it's only used for sorting and is simply the categories with any space in a name replaced by an underscore.
In creating the tempfile, the category is processed to contain underscores (the sortingcategory) and when the final placement is resolved, the underscores are removed returning the category name.
Negative weights should now be processed appropriately.
-- further revision for "not append *"
FOR /f "tokens=1-5delims=," %%s IN (q45196316.txt) DO (
SET "sortingcategory=%%s"
SET "sortingcategory=!sortingcategory: =_!"
FOR %%z IN ("!sortingcategory!") DO (
SETLOCAL disabledelayedexpansion
FOR /f "delims=" %%a IN (
'dir /b /a-d "%sourcedir%\%%~v%%u%%~w" 2^>nul'
AND
add 2 extra columns to the q45196316 file
music,6,music,*,*
music,8,Official video,"",*
conspiracies,3,misinform,*,*
conspiracies,6,kythera,*anti,*
missing,0,not appearing in this directory,*,*
rock music,2,metal,*,*
conspiracies,-5,negative,*,*
The for /f ... %%s now generates %%v and %%w containing the last two columns (as tokens is nor 1-5)
These are applied as prefix and suffix to %%u in the dir command. Note that "" should be used for nothing as two successive , are parsed as a single separator. The ~ before the v/w in %%~v means remove the quotes.

Why is the array filled with "m"

I'm fairly new to batch and I've been trying to write some simple sorting programs. This program uses the most basic sorting system, and the code(from what I can see) seems to be error-free. Yet when I run it, a random list is generated, and it seems as if some sorting is going on, then the array is filled with the letter "m". I don't see why this is occurring, so if somebody could point me in the correct direction I would greatly appreciate it.
My code:
#echo off
color b
title sorting
set ar=0
set num=0
set check=0
set checknum=0
set totalnumber=500
set randmax=5000
:array
if %num% LSS %totalnumber% (
set /A a[%num%]=%random% %% %randmax%
set /A num=%num%+1
goto array
)
if %num% EQU %totalnumber% (
goto echo1
)
:echo1
for /F "tokens=2 delims==" %%s in ('set a[') do echo %%s
echo sort initialized
goto sort
)
:sort
set n=0
:sortloop
set /A m=%n%+1
if %n% EQU %totalnumber% (
goto check
)
if %a[%n%]% GTR %a[%m%]% (
set hold=%a[%m%]%
set a[%m%]=%a[%n%]%
set a[%n%]=%hold%
set /A n=%n%+1
goto sortloop
)
if %a[%n%]% LSS %a[%m%]% (
echo a[%n%] check
set /A n=%n%+1
goto sortloop
)
:check
set check=0
set checknum=0
:checkloop
set /A checknumplus=%checknum%+1
if %check% EQU %totalnumber% (
goto complete
)
if %checknum% EQU %totalnumber% (
set n=0
goto sort
)
if %a[%checknum%]% LSS %a[%checknumplus%]% (
set /A check=%check%+1
set /A checknum=%checknum%+1
goto checkloop
)
:complete
for /F "tokens=2 delims==" %%s in ('set a[') do echo %%s
for /F "tokens=2 delims==" %%s in ('set a[') do echo %%s > sortedlist.txt
When you need to use variables inside of variables in batch (most commonly when working with arrays), you need to use delayed expansion.
Right now, your code says set hold=%a[%m%]%. The interpreter is treating this value as the variable %a[% (which doesn't exist, so it using nothing), the literal character m, and the variable %]% (which also doesn't exist and is therefore empty).
To get around this, put setlocal enabledelayedexpansion at the top of your code and then change your set statement to set hold=!a[%m%]! (and do the same thing with the other lines that are using it).

Print array element value in a Batch file

There is a problem with printing array element values in the below code:
#echo off
setlocal enabledelayedexpansion enableextensions
for /F "tokens=2,3 delims= " %%a in ('findstr "associationMaxRtx maxIncomingStream maxOutgoingStream initialAdRecWin maxUserdataSize mBuffer nThreshold PathMaxRtx maxInitialRtrAtt minimumRto maximumRto initialRto rtoAlphaIndex tSack" C:\Users\ephajin\logs.txt') do (
set /A count+=1
set vartmp1=%%a
set vartmp2=%%b
set "array[!count!]="%%a %%b""
)
(for /L %%i in (1,1,%count%) do echo !array[%%i]!
) > result.txt
in the result file I get the output
ECHO is off.
ECHO is off.
ECHO is off.
ECHO is off.
It does not print the array values.
The problem is probably due to setlocal enabledelayedexpansion but how do you correct it?
FOR /L %%a IN (1,1,4) DO ECHO !array[%%a]!
FOR /f "tokens=1*delims==" %%a IN ('set array[') DO ECHO %%b
Either of these two lines should show you what you appear to require.
Since the first is identical in effect to your code, I suspect that the array[*] array isn't being established correctly. you can check this by executing
set array[
to show precisely what has been set. Actually,
set
should show you all defined user-variables.
set|more
would show the same, but allow you to page through them.
SET "result="
FOR /f "tokens=1*delims==" %%a IN ('set array[') DO SET "result=!result! %%b"
ECHO result: "%result%" or "%result:~1%"
echo===============
SET "result="
FOR /L %%a IN (1,1,4) DO SET "result=!result! !array[%%a]!"
ECHO result: "%result%" or "%result:~1%"
Two methods of setting result - the list of the values in the array. Naturally, the space in the set instruction could be almost any character you desire - comma, for instance. The result is shown both with a leading space and with that space removed.

Calculating A Percentage Of System Ram With Batch Script (Using In Dynamic Memory Management Modification)

So I think I have this correct but for some reason it's not reading from the output file "ram.dat". Can anyone find the error in this?
#echo off
set percent=90
:ramcalc
cls
if %percent% GTR 90 Echo Needs To Be Less Than 90
if %percent% LSS 1 Echo Needs To Be Greater Than 1
echo Type Percent Of Ram To Calculate
set /p percent=1-90:
if %percent% GTR 90 goto ramcalc
if %percent% LSS 1 goto ramcalc
cls Calculating...
setlocal ENABLEDELAYEDEXPANSION
set vidx=0
for /F "tokens=*" %%A in (ram.dat) do (
SET /A vidx=!vidx! + 1
set var!vidx!=%%A
echo %%A
)
set var > test.txt
calc %var2%+%var3%+%var4%+%var5% >tmp
set /p add= < tmp
del tmp
calc %add%/1000000 >tmp
set /p divide= < tmp
del tmp
calc %divide%*0.%percent% >tmp
set /p ram= < tmp
del tmp
set /a round=%ram%+0
set ram=%round%
calc %ram%/1024 >tmp
set /p gb= < tmp
del tmp
set /a ramb=%gb%+0
cls
echo %percent% Rounded Is %ram%MB Approxamatly %ramb%GB
pause
goto ramcalc
I'm going to be using this in a dynamic memory modification and it is just a modified sample of my code.
I was typing the answer to your other question when it suddenly vanished. In that question you were dealing with three complexities. 1) Math operations in DOS are limited to 32-bit numbers. 2) WMIC outputs Unicode, so it throws off the for /F command. 3) endlocal discards the ram variable. Here's what I came up to solve all three problems:
#echo off
wmic memorychip get capacity>ram.dat
type ram.dat>ram.txt
setlocal ENABLEDELAYEDEXPANSION
set /p percent=Enter percentage (1-100):
set vidx=0
set var1=0
set var2=0
set var3=0
for /F "skip=1 delims=" %%i in (ram.txt) do (
if "!tmp!" NEQ "" (
SET /A vidx=!vidx! + 1
set var!vidx!=%%i
)
)
set /a var1/=1048576
set /a var2/=1048576
set /a var3/=1048576
set /a total=!var1!+!var2!+!var3!
set ram=!total!MB
del ram.dat
del ram.txt
endlocal &set /a ram=%total% * %percent% / 100
set ram=%ram%MB
set ram
This will calculate the ram for 3 memory card slots, leaving the ram environment variable set at the end of running the batch file. Your new code in your current question looks like you want to calculate a percentage of the available ram. I don't think you can do decimal math in DOS, so you're going to have to use another formula.
Edit 1
I updated the example to include the percentage you included in your second question. You still need to add the bounds checking.

Resources