I have a question about a piece of code I am writing.
Set /a 4DgtNum= %Random% %%9999
That's what is being used. I need to know how could I make a "Start at this number end in the range of another" code like start at (I.E 1000 and the max you can go to is 9999, but use any number between that ratio).
#ECHO Off
SETLOCAL enabledelayedexpansion
For %%b IN (#) DO FOR /F "delims==" %%a In ('set %%b 2^>Nul') DO SET "%%a="
FOR /L %%x IN (1,1,30000) DO CALL :choosernd&SET /a #!num!+=1
SET #
GOTO :eof
:choosernd
set /a min=1000
set /a max=9999
set /a min=1
set /a max=5
set /a rangesize=max-min+1
SET /a numranges=32767/rangesize
SET /a maxrange=rangesize * numranges-1
:chooseagain
set /a num=%RANDOM%
if %num% gtr %maxrange% GOTO chooseagain
SET /a num=(num %% rangesize) + min
rem ECHO %num%
GOTO :EOF
This question has been asked many times on SO. Here's a generate-and-test routine.
The meat of the matter is the :choosernd routine, where you set your minimum and maximum values.
I overrode your 1000 and 9999 with 1 and 5 for ease of testing.
First, calculate range size. That part should be obvious.
Next - the random-number generator returns %random% in the range 0..32767, so using %random% %% 100 for instance would return 328 numbers in 0..67, but 327 in 68..99. Hence, calculate the number of full ranges (327) available, and the maximum value to obtain an even distribution; (100*327)-1=32699.
Get a random number, but reject any that are greater than the maximum, so reject 32700..32767.
Perform a modulus operation on the chosen, filtered number and add the minimum, giving num
The code before this is simply clearing any variables whose names begin # and then executing the :choosernd routine 30,000 times. Each time, the variable #!num! is incremented, where delayedexpansion (which is explained many. many times on SO - use the search facility) is used to correctly choose a counter.
Then the counter is displayed for verification (the "random" generator is not particularly good)
Note that delayedexpansion is used only in the logging routine - it does not need to be invoked for the :choosernd routine.
Also - note that variablenames may not start with a digit.
When you use the point-click-and-giggle method of executing a batch, the batch window will often close if a syntax-error is found. You should instead open a 'command prompt' and run your batch from there so that the window remains open and any error message will be displayed.
Related
I need to create an integer array that stops at a limit number and then goes on after another fixed value. Something like [1 2 3 4 5 10 11 12 13 14 15 16], where 5 is the limit number and 10 where it restarts.
Can you do something like set arr = [1:%lim%, 10:%max%] ?
#ECHO OFF
SETLOCAL
CALL :buildlist mylist 1 5 10 16
ECHO list is %mylist%
PAUSE
GOTO :eof
:: Produce a list of integers
:: %1 is listname
:: %2 is start value
:: %3 is end-value for range
:: %4 is restart-value
:: %5 is end-value
:buildlist
SET "%1="
FOR /L %%v IN (%2,1,%5) DO (
IF %%v leq %3 CALL SET "%1=%%%1%% %%v"
IF %%v geq %4 CALL SET "%1=%%%1%% %%v"
)
CALL SET "%1=%%%1:~1%%"
GOTO :eof
Batch really doesn't have arrays, but can simulate one with a little imagination.
The above routine produces a list of values to the specification, ready for processing by a for statement.
Although it's possible to use delayedexpansion, it's possible to avoid that facility. By CALL ing a set command, the command is parsed before execution so to decode the hieroglyphics, where %1 is the variable name "var"
SET "%1=%%%1%% %%v"
is processed as:
SET "var=%var% %v"
as %% is an escaped-%, and %v will be replaced by the value of the metavariable (loop-control variable) %%v
Similarly,
SET "%1=%%%1:~1%%"
is executed as
SET "var=%var:~1%"
which deletes the first character, which will be a space.
This problem is about several for /L commands really, although it could be described in terms of nested if commands or in other different ways...
There are several different methods to give the values of the for commands, and also to implement they. I think this is the simplest one:
#echo off
setlocal EnableDelayedExpansion
set ranges="1:5" "10:16"
set "list="
for %%a in (%ranges::= 1 %) do for /L %%i in (%%~a) do set "list=!list! %%i"
echo list=%list%
Note that this method also works with several (more than two) ranges of values.
PS - This result is a list, not an array
Batch doesn't have OR operators, so you'll have to make do with two separate if statements - one for the first set of numbers and one for the second set of numbers. You can, however, put both of those in the same for loop:
#echo off
setlocal enabledelayedexpansion
set "lim=5"
set "max=10"
REM Setting counter to -1 because we're going to increment the counter and then
REM use it, and arrays famously start at zero.
set "counter=-1"
for /L %%A in (1,1,16) do (
if %%A LEQ %lim% (
set /a counter+=1
set "array[!counter!]=%%A"
)
if %%A GEQ %max% (
set /a counter+=1
set "array[!counter!]=%%A"
)
)
REM Display the contents of the array just to prove it worked.
REM Alphabetic order is used, so array[10] and array[11] are going to print before array[1]
set array[
I have a bit of (Bad) Code for encrypting text, but to be able to decrypt it needs to have something inbetween the numbers. I want to fit random letters inbetween the numbers so it looks less obvious, this is where i got to:
#echo off
setlocal enableDelayedExpansion
set /p code=Text:
set chars=0123456789abcdefghijklmnopqrstuvwxyz
for /L %%N in (10 1 36) do (
for /F %%C in ("!chars:~%%N,1!") do (
Set _Alphanumeric=ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz
Set _count=0
set _RNDLen=%random%
Set /A _RNDLen=_RNDLen%%4
If !_count! leq %_RNDLen% call :loop
set "code=!code:%%C=-%%N!"
)
)
echo !code!
echo !_str!
pause
:loop
Set /a _count+=1
set _RND=%random%
Set /A _RND=_RND%%51
SET _str=!_str!!_Alphanumeric:~%_RND%,1!
EXIT /B %ERRORLEVEL%
The problem is that the program just quits before giving any output, even if i remove the exit /b statement. Thanks for help
I've no idea what principle you're using for your algorithm, but fundamentally you need to understand delayed expansion.
When your outer loop, for %%N is parsed, every %var% is replaced by the contents of that variable at that time, hence
set _RNDLen=%random%
If !_count! leq %_RNDLen% call :loop
are replaed by
set _RNDLen=a specific random number
If !_count! leq call :loop
The first line here will set _rndlen to the same number every time (for ny run) and since _rndlen is undefined at the start of the loop, it willl be replaced by nothing, hence the if statement has faulty syntax and hence cmd objects and would display a message.
You can use !random! with delayed expansion invoked to select a rendom number each time, and you need !_rndlen! to access the changed value of _rndlen (changed from its original value of nothing to some random value and then mod-4'd)
Personally, I'd assign _alphanumeric outside of the (outer) loop since its value isn't varied by the loop operation.
And naturally, you know that when you hit Return following the pause, the loop code will be executed before the routine terminates (by flow-through) and you should include a
goto :eof
line after the pause to skip this last operation.
What's the syntax of the command that could abstract a variable from a basic, integered number?
For example
by executing a batch file, a variable (like a flag) is automaticaly set to 0, grows by 1 each time another specific command is executed and peaks at 10.
I want to know at any given point, how many times this flag-variable has grown, so I could get the exact times that the affiliated command has been executed and most importantly how many times this variable still has to grow.
So, thinking purely mathematicaly, I thought of abstracting 10 out of the variable to get my answers. But i can't make this to work on CMD.
I used this exact line in the code :
set /a 10-variable & ( echo executions of that command left )
But this only ends up showing executions of that command left without showing any number what so ever.
You have a logical quirk. set doesn't work the way you do it.
#echo off
set /a variable=0
:loop
set /a variable+=1
set /a left=10-%variable%
echo %left% executions left.
if %left% gtr 0 goto :loop
echo done.
Alternative (using only one variable instead of two):
set /a left=10
:loop2
set /a left-=1
echo %left% executions left.
if %left% gtr 0 goto :loop2
echo done.
Note: set /a var+=1 is just a short form of set /a var=%var%+1
As part of a program I'm making, I want to be able to 'plot' or 'mark' random points on a grid. In doing so I need to generate a random number(x) to determine the number of plots- and then x amount of different random numbers representing coordinates. At the moment I have the following code:
#echo off
:obno
set /a r1=%random%
if %r1% gtr 10 goto obno else (
goto ob
)
:ob
for /L %%R in (1,1,%r1%) do set /a n%%R=%random%*240/32678+1
echo %r1%
echo %n1% %n2% %n3% %n4% %n5% %n6% %n7% %n8% %n9% %n10%
pause
What happens here is I end up with x amount of the same random number instead of different ones
i.e. ouput:
5
108 108 108 108 108
so any help would be greatly appreciated!
In a loop you should be using delayed expansion e.g.
SETLOCAL ENABLEDELAYEDEXPANSION and using !RANDOM! instead of %RANDOM%
And...
%RANDOM% gives you a random number between 0 and 32767.
Using an expression like below you can change the range to anything you like (here the range is [1…100] instead of [0…32767]).
SET /A test=%RANDOM% * 100 / 32768 + 1
And below will produce number between 1~100
set /a num=%random% %%100 +1
To get a random number between 0 and 9 on the command line, use
set /a "rand=%random% % 10"
If used in a batch file then double the modulo operator
set /a "rand=%random% %% 10"
Using your code, you can use a call and double the % to make %%random%%
#echo off
:obno
set /a r1=%random%
if %r1% gtr 10 goto obno else (
goto ob
)
:ob
for /L %%R in (1,1,%r1%) do call set /a n%%R=%%random%%*240/32678+1
echo %r1%
echo %n1% %n2% %n3% %n4% %n5% %n6% %n7% %n8% %n9% %n10%
pause
Here is a simliar post to generate random numbers in a batch file
You need to include the following at the start of your batch file
setlocal EnableDelayedExpansion
Random variable not changing in "for" loop in windows batch file
#ECHO OFF
SETLOCAL
:obno
set /a r1=%RANDOM% %% 10 + 1
:ob
for /L %%R in (1,1,%r1%) do CALL set /a n%%R=%%random%% %%%% 240 + 1
echo %r1%
echo %n1% %n2% %n3% %n4% %n5% %n6% %n7% %n8% %n9% %n10%
GOTO :EOF
Since %random% produces a random number 0..32767, your original code will waste a lot of time waiting for a number between 0 and 10. Also, it may generate 0.
The % operator is used for 'modulus'. Within a batch, you need to double it, so %random% %% 10 produces 0..9
By CALLing a command, you can avoid the need to setlocal enabledelayedexpansion BUT you also need to redouble any % you use, hence the doubling around random and quadrupling for the mod operator.
The setlocal isn't strictly necessary, BUT what it does is back out enviroment changes when the batch ends, so r1,n1..n10 simply no longer exist in the environment once the batch finishes. If you OMIT the setlocal then any variable set in one run will REMAIN set for a subsequent run, so if your first run sets n5 (and also therefore n1..n4) then if the second run sets r1=2, n3..n5 would be displayed with the stale data from the previous run.
Finally, if you allow r1=0 then since none of n1..n10 are set, the echo %n1%... command will be resolved to echo, will will show the echo status (Echo is on/off). This can be overcome by using the syntax echo(%n1%.... The character directly following the O has been found to do some strange things.
echoDot for instance is traditional for a blak newline. The ( may look odd and appear to 'unbalance' the nesting, but works quite happily to produce a newline with empty arguments.
Let's say that I have a batch file that reads arbitrary integers from a file. The file is structured such that each line contains one integer, like so:
24
17
43
103
...
I need to calculate the average of the top 20 numbers that are in the file. In order to do that, I need some sort of data structure that stores the top 20 numbers. However, as far as I know there are no arrays in batch files. I may have to resort to using temporary files or some other method that I am not aware of. So my ultimate goal is to determine the best approach for implementing some sort of sorting algorithm for the batch file and calculate the average of the top 20 integers.
There is a constraint that I need to place on the problem. The file is pretty huge in terms of size (around ~500 lines) so I would rather not use temporary files due to the huge amount of read/write operations done (unless if you can convince me otherwise of course).
You can mimic arrays in batch. Take a look at Using Arrays in Batch Files.
The following solution uses arrays as described in the article by the link provided in the #Stoney's answer. It also uses zero-padding for correct sorting, which is a nice idea by #jeb, although this solution doesn't use the sort command. Instead, the sorting is automatically done by the SET command, whose output is used for iterating over the 'array'.
#ECHO OFF
SET top=5
SET cnt=0
FOR /F %%N IN (datafile) DO CALL :insert %%N
IF %cnt%==0 GOTO :EOF
IF %cnt% LSS %top% (SET threshold=0) ELSE SET /A threshold=cnt-top
SET s=0
SET i=0
FOR /F "tokens=2 delims=.=" %%A IN ('SET __number.') DO CALL :calc %%A
SET /A res=s/(cnt-threshold)-1000000
ECHO Average is %res%
PAUSE
GOTO :EOF
:insert
SET /A n=1000000+%1
SET /A __number.%n%+=1
SET /A cnt+=1
GOTO :EOF
:calc
SET /A i_prev=i
SET /A i+=__number.%1
IF %i% LEQ %threshold% GOTO :EOF
IF %i_prev% GEQ %threshold% (
SET /A s+=%1*__number.%1
) ELSE (
SET /A "s+=%1*(i_prev+__number.%1-threshold)"
)
Basically, the solution implements the following algorithm:
Pick numbers from the file one by one:
1.1. If the number is encountered for the first time, add it to the array with the count of 1.
1.2. If the number is a duplicate of an already added number, increase the corresponding count value by 1.
1.3. Increase the total count of numbers by 1.
Calculate the threshold value, which is the total count minus the top quantity of numbers whose average is to be calculated.
Iterate through the array items like this:
3.1. Increase the index by the current number's count value.
3.2. If the index exceeds the threshold:
If it has exceeded the threshold at the current iteration, increase the total sum by the product of the number and that part of its count that has exceeded the threshold.
If the threshold was exceeded earlier, increase the total sum by the product of the number and its count.
3.3. If the index doesn't exceed the threshold, omit the item.
Calculate the average as the total sum divided by the difference between total count and threshold.
You can use the sort command to sort the numbers, but there is the porblem, that sort uses a string sort and does not sort numbers, so a 2 seems to be greater than 10.
But this can be solved if you format all numbers to the same length into a temporary file.
So you get
024
017
043
103
...
Sort them with the /R (Reverse) option, to begin the output with the biggest number.
Then you can simply read 20 lines and build the sum for the average
for small files:
#Echo oFF
for /f %%a in (f1.txt) do Call :Append %%a
call :sort %sort%
pause
goto :EOF
:Append
call set sort=%%sort%% %*
goto :eof
:Sort
Setlocal EnableDelayedExpansion
Set/A n=1,s=0,c=s,r=s
for %%: In (%*) do (
Set /a c+=1
Set "nm.!c!=%%:")
:LP.1
if %s% EQU %c% Set/A n+=1,s=0
Set/A s+=1
Call :SPL %n% %s%
If %n% LEQ %c% goto :LP.1
:LP.2
Echo:!nm.%c%!
Set/A c-=1
If %c% GTR 0 goto :LP.2
Endlocal & goto :EOF
:SPL
If !nm.%1! GTR !nm.%2! (
Set "t=!nm.%2!"&Set "nm.%2=!nm.%1!"
Set "nm.%1=!t!"
)
goto :EOF
or a variant:
#echo off
for /f %%# in (f1.txt) do (
set x=##########%%#
call set #%%x:~-0xa%%==)
for /f "delims=#=" %%a in ('set ##') do echo(%%a
pause
there are also: gnu sort