0

My batch file is trying to access random numbers stored in an simple list array by assigning the random numbers in the array to a variable. The batch file will then loop through to the next number.

@echo off
setlocal enabledelayedexpansion
set /a count=0
:loop
set /a num=%random% %% 135 + 1
if "!numlist!" == "" (
    set numlist=!num!
    set /a count+=1
) else (
    for %%i in (!numlist!) do (
        if !num! == %%i (
            goto loop
        )
        if !num! == %%i+1 (
            goto loop
        )
        if !num! == %%i-1 (
            goto loop
        )
    )
    set numlist=!numlist! !num!
    set /a count+=1
)
if !count! lss 135 (
    goto loop
)
echo !numlist!

set /a i=0
for %%n in (!numlist!) do (
    set arr[!i!]=%%n
    set /a i+=1
)
rem Display the array elements
for /l %%i in (0,1,135) do (
echo !arr[%%i]!

)

rem Set the path to the folder containing the files to be renamed
set folderPath="D:\Path\To\Files"

rem Rename up to 135 files in the specified folder using the random numbers from the array
set /a g=0
for %%f in ("%folderPath%\*.*") do (
    if !g! geq 135 goto endloop
rem echo var g is !g!
    set "num=!arr[!g!]!"
rem echo var num is !num!
    ren "%%f" "!num! - %%~nxf"
    set /a g+=1
    set /a i+=1
)
:endloop
endlocal

As only an occasional coder I took a long time to write this script. It is designed to add a random number to the filename of a folder containing 135 files (this will be modified to add a filenum variable for use on any folder). The numbers will not be duplicated or consecutive.

It works as expected until the set "num=!arr[!g!]!" line. The value of variable "num" is not set to the random number from the array but is set to "g" in every iteration of the loop giving the output "g - [file_name]. extension" and not "[random_number] - [file_name].extension".

I have spent hours googling and experimenting but I can't find a solution. I have a feeling it may be something obvious to a more experienced coder. I would be grateful to be pointed in the right direction.

Moldy1
  • 11
  • 3
  • delayed variables cannot be nested within each other. the interpreter sees: `!arr[!g!]!` as two variables: `!arr[!` and `!]!` with the letter g in between, and as they are undefined, all you get is `g`. If you need to expand a delayed variable using another delayed variable while in a code block, use: `For /f "delims=" %%G in ("!g!")Do Set "num=!arr[%%G]!"` – T3RR0R May 17 '23 at 15:58

2 Answers2

0

This should set you in the right direction

It does not completely cure your problem

@ECHO OFF
setlocal ENABLEDELAYEDEXPANSION

SET maxc=0

:again
set /a count=0
SET "numlist="
:loop
set /a num=%random% %% 135 + 1
IF "%numlist%" neq "" (
 for %%i in (%numlist%) do (
  if %num% == %%i (
     goto loop
  )
  SET /a num+=1
  if !num! == %%i (
     goto again
  )
  SET /a num-=2
  if !num! == %%i (
     goto again
  )
  SET /a num+=1
 )
)
set numlist=%numlist% %num%
set /a count+=1
IF %count% gtr %maxc% (
 ECHO %count% %TIME%
 SET /a maxc=count
)
if %count% lss 29 (
    goto loop
)
echo %numlist%

set /a i=0
for %%n in (%numlist%) do (
    set arr[!i!]=%%n
    set /a i+=1
)
rem Display the array elements
for /l %%i in (0,1,28) do (
echo !arr[%%i]!

)
ECHO %time%

rem Set the path to the folder containing the files to be renamed
set folderPath="D:\Path\To\Files"

rem Rename up to 11 files in the specified folder using the random numbers from the array
set /a g=0
for %%f in ("%folderPath%\*.*") do (
    if !g! geq 29 goto endloop
rem echo var g is !g!
    set "num=%arr[!g!]%"
rem echo var num is !num!
ECHO    ren "%%f" "!num! - %%~nxf"
    set /a g+=1
    set /a i+=1
)
:endloop

GOTO :EOF

We have !madness. %var%==!var! EXCEPT within a loop, where %var% is the value of var before the loop started to be executed and !var! the value as it may have changed in the loop.

So - implicitly, !var! is the value as the loop changes var and %var% is the value otherwise.

maxc is the maximum count of numlist entries found.

In your code, for the %%i loop, num is not changed within the loop, and numlist is not used after it has been changed, so %var% is the correct syntax.

if %num% == %%i+1

does not evaluate %%i+1, so the comparison is performed as (eg) 37 == 36+1 not 37 == 37 and will thus always be FALSE. Hence, your pre/succeeding number detection will fail.

I have replaced that method with one that demonstrates the use of %var% against !var!. Given that num is 37 for instance, then test 37 against %%i, add 1 to num and test 38 against %%i, subtract 2 and test 36 against %%i, and finally add 1 again to restore the original num.

The first time around, numlist will be empty, so all of this testing is bypassed and num appended to numlist. Once the first number has been added to numlist, that testing will be executed.

I added a little code to demonstrate what's happening with your next problem. Test count to see whether it's reached a new maximum. If it has, record the new maximum found in maxc and report the count and time.

You will find that the routine will become slower and slower in reporting the count and time. This is because you are testing EACH number in numlist to num, num+1 and num-1 when you should be testing only the last number chosen against num.

Ah! - But I've changed the +/-1 to goto again, not loop. True, but suppose your only numbers left are all one different from (any number in numlist). We can never choose a valid number, so we'll get an infinite loop.

Even with the correction made to test only the last number chosen against num, suppose we have only 15,16 unchosen (not in numlist). We again have an infinite-loop situation, as we can only choose successive numbers.

So - I changed the limit to 29 numbers (1..135) as that took <2 mins on my machine. Setting it higher would produce an infinite loop or take an unacceptable time.

Note that having chosen the numbers in numlist, you are entering them into a 0-based array, so 28 is used here.

Finally, we arrive at the renaming loop. I haven't run this, just fixed it in theory.

note that g and num are being changed within the loop, but arr* is not. Your code set "num=!arr[!g!]!" would set num to (the current value of arr[)+g+(the current value of ]) which most likely evaluates to g.

By echoing the ren command, no harm is done if the ren is not as expected.

And there's no apparent reason for incrementing i.

Magoo
  • 77,302
  • 8
  • 62
  • 84
  • This command don't works: `set "num=%arr[!g!]%"` – Aacini May 17 '23 at 18:51
  • @Aacini : QUite so. Should be `CALL set "num=%%arr[!g!]%%"` – Magoo May 17 '23 at 19:17
  • My problem is now solved and given the contributions from the above expert coders some large gaps in my knowledge have been filled. My modified script now runs really well and I can now move on to the next stage of my project. Thanks. – Moldy1 May 19 '23 at 10:02
0

Mmmm... Let's analyze your code a little before trying to fix the problem...

In your code you first generate 135 random numbers that are not consecutive, right?

Well, the first if just works one time when numlist is empty. After that, the rest of times the else part is executed... This seems a waste of code, isn't it? Why not just initialize the list with the first number the first time? In this way you can entirely eliminate the (useless) if:

@echo off
setlocal EnableDelayedExpansion

set /A numlist=%random% %% 300 + 1, count=1

:loop
   set /A num=%random% %% 300+1, numP1=num+1, numM1=num-1

After that, you process all numlist elements one by one in order to compare each element vs num (and numP1 and numM1). You should note that numlist is a string (not an array), so you can easily process the whole string via !numlist:%num%=! syntax: if the result is different than before, then %num% is contained in the string! ;)

   if "!numlist:%num%=!" neq "%numlist%" goto loop
   if "!numlist:%numP1%=!" neq "%numlist%" goto loop
   if "!numlist:%numM1%=!" neq "%numlist%" goto loop

   set "numlist=%numlist% %num%"
   set /A count+=1
if %count% lss 135 goto loop
echo %numlist%

rem Copy "numlist" items into "arr" array
set "i=0"
for %%n in (%numlist%) do (
   set /A i+=1
   set "arr[!i!]=%%n"
)

rem Display the array elements
for /L %%i in (1,1,135) do echo !arr[%%i]!

Note that:

  • There is no way to generate 135 non consecutive numbers in the range 1..135. You need the double range at least! As the range is greater, the process will take less time. I used 300.
  • Parts in if command are enclosed in quotes in order to make they more readable.
  • The inclusion of quotes in set "var=value" command is a good practice that eliminate any unnoted blank space at end of the value.
  • I used to start array elements at 1 (instead of common C's 0), this aids to avoid subtle errors. For example, in your code the for /l %%i in (0,1,135) do is wrong: it should be (0,1,134)

The second part is almost correct excepting this line: set "num=!arr[!g!]!". You could use call set "num=%%arr[!g!]%%" or for %%g in (!g!) do set "num=!arr[%%g]!" as described with detail at this answer

Aacini
  • 65,180
  • 12
  • 72
  • 108