?
Solved

Batch File: Random without duplicates

Posted on 2011-10-17
20
Medium Priority
?
422 Views
Last Modified: 2012-05-12
Hi there,

I scripted this batch file that will read a file containing a list of emails, and draw a defined quantity of winning emails for a prise.

I need to add to my script a feature to prevent duplicate winners while still reaching the quantity of winning emails, by tweeking my script.

Thanks for your help,
Rene

@echo off
setlocal enabledelayedexpansion
IF "%~1" EQU "" (
	ECHO PLEASE DRAG AND DROP YOUR EMAIL LIST FILE.
	ECHO IN A TEXT FORMAT ".txt" NOT EXCEL FORMAT.
	PAUSE
	EXIT
)

IF "%~x1" NEQ ".txt" (
	ECHO YOUR LIST FILE MUST BE IN A TEXT FORMAT ".txt".
	PAUSE
	EXIT
)

SET Output=%~dpn1_Winners%~x1

ECHO Please enter the number of desired winners
SET /P WinnersNumber=

SET Count=0
FOR /F "usebackq delims=" %%A IN ("%~1") DO SET /a Count+=1
ECHO There are [%Count%] email(s) and [%WinnersNumber%] winners to be.
ECHO.

FOR /L %%G IN (1,1,%WinnersNumber%) DO (
	ECHO Draw Number:%%G
	PING 127.0.0.1 -n 1 >NUL
	FOR /L %%A IN (1,1,50) DO (
		SET /a Val = !random! %% %Count% + 1
		TITLE !Val!
		PING 127.0.0.1 -n 1 >NUL
	)
	CALL :Draw "%~1"
	ECHO [!TheWinnerIs!]
	ECHO %date%,%time%,!TheWinnerIs!>>"%Output%"
	ECHO.
)

PAUSE
EXIT

:Draw
SET /a ValSkip=%Val% - 1
FOR /F "usebackq skip=%ValSkip% delims=" %%A IN ("%~1") DO (
	SET TheWinnerIs=%%A
	exit /B
)

Open in new window

0
Comment
Question by:ReneGe
  • 6
  • 6
  • 5
  • +1
20 Comments
 
LVL 43

Expert Comment

by:Steve Knight
ID: 36983080
Rene,

Haven't got time to do it at the mo. but would probably keep a list of each as it was found in a text file and then use a find or findstr as each next one is drawn against that... either that or against a an environment string.  

Steve
0
 
LVL 10

Author Comment

by:ReneGe
ID: 36983104
Thanks Steve.

My challange here would be that if it finds a duplicate, it will skip it and therefore, will generate less winners.

Cheers
0
 
LVL 43

Expert Comment

by:Steve Knight
ID: 36983160
Replace

SET /a Val = !random! %% %Count% + 1
with call :getrandom


:getrandom
  SET /a Val = !random! %% %Count% + 1
  find "!random!" yourlog.txt && goto getrandom
exit /b

or something like that? i.e. keep looping until random is what you want.... and then add !random! to the end of the yourlog.txt file.

Make sure yourlog.txt is fresh each time.

Bed now...

Steve
0
 [eBook] Windows Nano Server

Download this FREE eBook and learn all you need to get started with Windows Nano Server, including deployment options, remote management
and troubleshooting tips and tricks

 
LVL 11

Expert Comment

by:paultomasi
ID: 36983312
As posted in another thread, I would use something along the following lines.
@echo off
copy /y nul random.txt >nul
set count=0

:loop
   set /a rnd=%random% %% 1000 +1
   findstr /b /e "%rnd%" random.txt

   if %errorlevel% equ 1 (
      echo %rnd% >> random.txt
      set /a count+=1
   )

if %count% lss 50 goto :loop

for /f %%a in (random.txt) do echo %%a

Open in new window

0
 
LVL 59

Assisted Solution

by:Bill Prew
Bill Prew earned 400 total points
ID: 36983313
I'm thinking the same way as Steve here, but rather than write to a log file and have to read it, why not just create temporary variables and check if they exist.  Maybe something like:

:getrandom
  SET /a Val = !random! %% %Count% + 1
  if defined got_!random! goto getrandom
  set got_!random!=Y
  exit /b

Open in new window

since you're using setlocal these will be purged when the BAT exists.

~bp
0
 
LVL 43

Assisted Solution

by:Steve Knight
Steve Knight earned 400 total points
ID: 36984051
good plan bill, i was getting at that in first post but didnt elaborate, though your way is more elegant and simple than I had in mind!  I was thinking record them in one variable comma delimited and use string replacement to see if it was in there, i.e.

Already = of form 123,456,789

:loop
  Set thisnumber=!random!
  if not "!already:!thisnumber!;=X!==!already! Goto :loop

but that gets messy sometimes with delayedexpansion...

Steve
0
 
LVL 11

Accepted Solution

by:
paultomasi earned 1200 total points
ID: 36985206
I was thinking large!

Naturally, for 10 or so numbers, it is more efficient to use the environment variable space. And of course, I'm a great advocator of using arrays in DOS batch file programs anyway.
@echo off

rem Reset any rnd[] variables
for /f "tokens=1 delims==" %%a in ('set rnd[ 2^>nul') do set %%a=

rem We need 50 so...
set count=50

rem Generate 50 unique random numbers
:loop
   set /a R=%random% %% 1000 +1
   if not defined rnd[%R%] (
      set rnd[%R%]=%R%
      set /a count-=1
   )
if %count% gtr 0 goto :loop

rem Display the random numbers
for /f "tokens=2 delims==" %%a in ('set rnd[ 2^>nul') do echo %%a

Open in new window

0
 
LVL 43

Expert Comment

by:Steve Knight
ID: 36985296
Paul - if you use setlocal then any RND[] variables would disappear after the batch run ended anyway surely.... though doesn't hurt to remove any accidentally left there I suppose.

Steve
0
 
LVL 11

Expert Comment

by:paultomasi
ID: 36985604
Steve... it's all yours and bill's now...

One thing I will point out before I go though is that doing it this way subjects the variables to alpha-numerical ordering (because that's the nature of the environment table). You would need to tweak the variable names so that as much of the 'randomness' is retained as possible.

Writing the values to a file (http:#36983312) eliminates this problem altogether.

0
 
LVL 59

Expert Comment

by:Bill Prew
ID: 36986813
Thanks Steve.

Paul, we are only checking the existence of the variable, it's order in the environment table doen't matter.

~bp
0
 
LVL 10

Author Comment

by:ReneGe
ID: 36986907
I'm now back on this.
Will follow-up in few moments.
Thanks guys!!
0
 
LVL 11

Expert Comment

by:paultomasi
ID: 36987950
billprew - Ah right, I see what you mean now.... used as a form of validation ie, check and use rather than, check, store all, retrieve and use... In that case variables are good for 50 values.

One thing I noticed earlier is that (contrary to previous results) when running the following code I was able to generate random numbers all the time.... Do computers generally play tricks on us or only when we're dead beat?
@echo off
setlocal enabledelayedexpansion

for /l %%a in (1,1,50) do (
   set /a rnd=!random! %% 1000 +1
   echo !rnd!
)

Open in new window

0
 
LVL 59

Expert Comment

by:Bill Prew
ID: 36988150
Paul,

Try changing the 1000 to 100, you'll get some collisions quicker there.  The concept is the same though, but since we are mapping the random number to a smaller range, there is a possibility of collision.

~bp
0
 
LVL 43

Expert Comment

by:Steve Knight
ID: 36988666
silly question here.... But ive never used the %% syntac in set /a .... Dont remember seeing in the help  text, could you explain what it is doing.... Seems to be saying make maximum of 1000?  how does it know what the maximum number would be from %random% ?



0
 
LVL 10

Author Comment

by:ReneGe
ID: 36988708
I am also curious about Steve's question.

Here is what I have so far.

My only challange here, is to show different random numbers in the title when "SHAKING THE BUCKET"

Thanks

@ECHO OFF
SETLOCAL enabledelayedexpansion

REM TESTING EMAIL LIST
	IF "%~1" EQU "" (
		ECHO PLEASE DRAG AND DROP YOUR EMAIL LIST FILE.
		ECHO IN A TEXT FORMAT ".txt" NOT EXCEL FORMAT.
		PAUSE
		EXIT
	)

	IF "%~x1" NEQ ".txt" (
		ECHO YOUR LIST FILE MUST BE IN A TEXT FORMAT ".txt".
		PAUSE
		EXIT
	)

REM SETTING STATIC VARIABLES
	SET Output=%~dpn1_Winners%~x1

REM DEFINING THE QUANTITY OF WINNERS
	ECHO Please enter the number of desired winners
	SET /P WinnersNumber=
	ECHO.

REM DEFINING THE NUMBER OF PERTICIPANT
	SET Count=0
	FOR /F "usebackq delims=" %%A IN ("%~1") DO (
		SET /a Count+=1
		SET Participant.!Count!=%%A
	)
	ECHO There are [%Count%] email(s) and [%WinnersNumber%] winners to be.
	ECHO.

IF "%WinnersNumber%" EQU "" (ECHO YOU MUST ENTER A NUMBER GREATER THAN 0. PLEASE TRY AGAIN & ECHO. & PAUSE & EXIT)
IF  %WinnersNumber%  EQU 0  (ECHO YOU MUST ENTER A NUMBER GREATER THAN 0. PLEASE TRY AGAIN & ECHO. & PAUSE & EXIT)
IF  %WinnersNumber%  EQU 1  ECHO AND THE WINNER IS:
IF  %WinnersNumber%  GTR 1  ECHO AND THE WINNERS ARE:

REM DEFINING WINNERS
	:Loop
	PING 127.0.0.1 -n 2 >NUL
	::
	REM SHAKING THE BUCKET CONTAINING PARTICIPANTS, 50 TIMES
		FOR /L %%A IN (1,1,50) DO (
			SET /a Rnd = %random% %% %Count% + 1
			TITLE !Rnd!
			PING 127.0.0.1 -n 1 >NUL
		)
	::
	IF DEFINED Winner.%rnd% GOTO Loop
	SET Winner.%Rnd%=!Participant.%Rnd%!
	SET /a WinnersNumber-=1
	ECHO !Participant.%Rnd%!
	IF %WinnersNumber% NEQ 0 Goto Loop

ECHO.
PAUSE
EXIT

Open in new window

0
 
LVL 10

Author Comment

by:ReneGe
ID: 36988895
It seems that it's because it's in a FOR loop (lines 45 to 49).

Inspired by Paul's scripts, I replaced it for a GOTO loop.

It's now working as required.

I'll split points evenly, but I'll add more to Paul case I owe hime some for me previous related question. Cause I did not realised that the post was from him and did not include him (thougt it was Bill) in the points sharing.
@ECHO OFF
SETLOCAL enabledelayedexpansion

REM TESTING PARTICIPANT LIST
	IF "%~1" EQU "" (
		ECHO PLEASE DRAG AND DROP YOUR EMAIL LIST FILE.
		ECHO IN A TEXT FORMAT ".txt" NOT EXCEL FORMAT.
		PAUSE
		EXIT
	)

	IF "%~x1" NEQ ".txt" (
		ECHO YOUR LIST FILE MUST BE IN A TEXT FORMAT ".txt".
		PAUSE
		EXIT
	)

REM SETTING STATIC VARIABLES
	SET Output=%~dpn1_Winners%~x1
	SET BucketLoopNb=50

REM DEFINING THE QUANTITY OF WINNERS
	ECHO Please enter the number of desired winners
	SET /P WinnersNumber=
	CLS

REM DEFINING THE NUMBER OF PERTICIPANT
	SET Count=0
	FOR /F "usebackq delims=" %%A IN ("%~1") DO (
		SET /a Count+=1
		SET Participant.!Count!=%%A
	)
	ECHO There are [%Count%] email(s) and [%WinnersNumber%] winners to be.
	ECHO.

IF "%WinnersNumber%" EQU "" (ECHO YOU MUST ENTER A NUMBER GREATER THAN 0. PLEASE TRY AGAIN & ECHO. & PAUSE & EXIT)
IF  %WinnersNumber%  EQU 0  (ECHO YOU MUST ENTER A NUMBER GREATER THAN 0. PLEASE TRY AGAIN & ECHO. & PAUSE & EXIT)
IF  %WinnersNumber%  EQU 1  ECHO AND THE WINNER IS:
IF  %WinnersNumber%  GTR 1  ECHO AND THE WINNERS ARE:
ECHO ----------------------

SET BucketLoop=%BucketLoopNb%

REM DEFINING WINNERS
	:Loop

REM SHAKING THE BUCKET CONTAINING PARTICIPANTS, X TIMES
	SET /a Rnd = %random% %% %Count% + 1
	TITLE !Participant.%Rnd%!
	SET /a BucketLoop-=1
	IF %BucketLoop% GTR 0 GOTO Loop
	SET BucketLoop=%BucketLoopNb%

REM DEFINING THE WINNER(S)
	IF DEFINED Winner.%rnd% GOTO Loop
	SET Winner.%Rnd%=!Participant.%Rnd%!
	SET /a WinnersNumber-=1
	ECHO !Participant.%Rnd%!
	IF %WinnersNumber% NEQ 0 Goto Loop

TITLE LIST OF WINNERS

ECHO.
PAUSE
EXIT

Open in new window

0
 
LVL 10

Author Closing Comment

by:ReneGe
ID: 36988904
Excellent team work!!

Thanks guys!!

Cheers,
Rene
0
 
LVL 43

Expert Comment

by:Steve Knight
ID: 36989456
Ahh after a bit of testing I see that % is "MOD", i.e. remainder from division then... and double %% due to being in batch file.

It would be nice if set /? actually said WHAT the operators did rather than just listing them, likewise if I knew what the h**l they were talking about with *= /= %= += etc!  I understand and xor or etc. of course, though who decided to use !^&<>% etc. in a batch file needs their head examining with having to escape them all with ^, % etc....

Well learn something every day and all that!

Steve
0
 
LVL 11

Expert Comment

by:paultomasi
ID: 36989524
ReneGe

Thank you.

I wasn't concerned about the points. I enjoy the challenge.

0
 
LVL 10

Author Comment

by:ReneGe
ID: 36989641
@Paul: For me, points are tokens of appreciation, not money.  ;-)
0

Featured Post

NEW Veeam Backup for Microsoft Office 365 1.5

With Office 365, it’s your data and your responsibility to protect it. NEW Veeam Backup for Microsoft Office 365 eliminates the risk of losing access to your Office 365 data.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Make the most of your online learning experience.
Q&A with Course Creator, Mark Lassoff, on the importance of HTML5 in the career of a modern-day developer.
The viewer will learn how to look for a specific file type in a local or remote server directory using PHP.
Progress
Suggested Courses
Course of the Month16 days, 3 hours left to enroll

850 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question