Batch file 如何在纯批处理脚本中生成无重复的随机数列表?

Batch file 如何在纯批处理脚本中生成无重复的随机数列表?,batch-file,random,Batch File,Random,我想生成一个随机数列表,其中包含预定义数量的项目%RND\u TOTAL%,范围从%RND\u MIN%到%RND\u MAX%,并具有一定的间隔%RND\u INTER%。当然,这可以通过以下代码片段实现: @echo关闭 setlocal EnableExtensions EnableDelayedExpansion rem随机数的总数: 设置/A“RND_总计=8” 随机数的rem范围(最小、最大、间隔): 设置/A“RND_最小值=1,RND_最大值=10,RND_中间值=1” 通过随机

我想生成一个随机数列表,其中包含预定义数量的项目
%RND\u TOTAL%
,范围从
%RND\u MIN%
%RND\u MAX%
,并具有一定的间隔
%RND\u INTER%
。当然,这可以通过以下代码片段实现:

@echo关闭
setlocal EnableExtensions EnableDelayedExpansion
rem随机数的总数:
设置/A“RND_总计=8”
随机数的rem范围(最小、最大、间隔):
设置/A“RND_最小值=1,RND_最大值=10,RND_中间值=1”
通过随机数的rem循环:
对于/L%%I in(1,1,%RND_总计%)do(
rem计算一个随机数:
设置/A“RND\U数量[%%I]=!随机!%((RND\U最大值-RND\U最小值)/RND\U中间值+1)*RND\U中间值+RND\U最小值”
echo!RND_NUM[%%I]!
)
端部
退出/B
下面是相应输出的一个示例(
4
9
在这里都是apear两次):

但是如何创建这样一个没有任何重复项的列表呢


当然,我可以使用下面的脚本来检查每个项目是否在数组中可用,比如变量
RND_NUM[]
,但是这种方法效率很低,因为嵌套了
for/L
循环,特别是当
%RND_TOTAL%
由于多次重复计算而接近范围规范所涵盖的可用随机数计数时:

@echo关闭
setlocal EnableExtensions EnableDelayedExpansion
rem随机数的总数,重复标志(`0`表示无重复):
设置/A“RND\U总计=20,标志\U DUP=0”
随机数的rem范围(最小、最大、间隔):
设置/A“RND_最小值=1,RND_最大值=30,RND_中间值=1”
rem通过随机数循环,在子例程中生成它们:
对于/L%%I in(1,1,%RND_总计%)do(
电话:SUB%%I
echo!RND_NUM[%%I]!
)
端部
退出/B
:SUB
rem获取已收集的随机数:
设置/A“RND\U计数=%1-1”
:循环
rem计算一个随机数:
设置/A“RND\U数量[%1]=!随机!%((RND\U最大值-RND\U最小值)/RND\U中间值+1)*RND\U中间值+RND\U最小值”
rem检查前一个集合中是否出现随机数:
如果%FLAG\u DUP%eq 0(
对于/L%%I in(1,1,%RND\u计数%)do(
如果遇到重复,rem重新计算随机数:
if!RND_NUM[%1]!eq!RND_NUM[%%I](
转到:循环
)
)
)
退出/B
这是相关的示例输出(此处无重复项):


我稍微修改了一段时间前编写的代码:

@echo off
setlocal EnableDelayedExpansion

rem total number of random numbers:
set /A "RND_TOTAL=8"
rem range for random numbers (minimum, maximum):
set /A "RND_MIN=1, RND_MAX=10"

set /A "range=RND_MAX-RND_MIN+1"

rem Create an input list with all numbers in given range
set "input="
for /L %%i in (%RND_MIN%,1,%RND_MAX%) do (
   set "input=!input! %%i"
)
set "input=%input% "
echo  IN: [%input%]

rem Extract RND_TOTAL elements from input list in random order
set "output="
for /L %%i in (%RND_TOTAL%,-1,1) do (
   set /A "randIndex=(!random!*range)/32768+1, range-=1"
   call :MoveInputToOutput !randIndex!
)
echo OUT: [%output%]

goto :EOF

:MoveInputToOutput randIndex
for /F "tokens=%1" %%n in ("%input%") do (
   set output=%output% %%n
   set input=!input: %%n = !
)
exit /B
输出示例:

> test
 IN: [ 1 2 3 4 5 6 7 8 9 10 ]
OUT: [ 8 7 9 3 1 4 5 2]

> test
 IN: [ 1 2 3 4 5 6 7 8 9 10 ]
OUT: [ 9 2 10 6 4 8 5 1]

> test
 IN: [ 1 2 3 4 5 6 7 8 9 10 ]
OUT: [ 1 2 4 3 8 5 7 9]
对不起,我不明白什么是
%RND\u INTER%
值用于

编辑:使用
%RND\u INTER%
值且不限制生成的随机数的新版本

@echo off
setlocal EnableDelayedExpansion

rem total number of random numbers:
set /A "RND_TOTAL=8"
rem range for random numbers (minimum, maximum, interval):
set /A "RND_MIN=1, RND_MAX=10, RND_INTER=1"

rem Create an input vector with all numbers in given range
set "n=0"
for /L %%i in (%RND_MIN%,%RND_INTER%,%RND_MAX%) do (
   set /A n+=1
   set "in[!n!]=%%i"
)
echo Input:
set in[
echo/

rem Extract RND_TOTAL elements from input vector in random order
for /L %%i in (1,1,%RND_TOTAL%) do (
   set /A "randIndex=(!random!*n)/32768+1"
   set /A "out[%%i]=in[!randIndex!], in[!randIndex!]=in[!n!], n-=1"
)
echo Output:
set out[
替换

for /L %%I in (1,1,%RND_TOTAL%) do (
    rem compute a random number:
    set /A "RND_NUM[%%I]=!RANDOM!%%((RND_MAX-RND_MIN)/RND_INTER+1)*RND_INTER+RND_MIN"
    echo !RND_NUM[%%I]!
)


每次选择时,
rnd_num{selectionmade}
被设置为
Y
,因此如果再次选择,它将被定义并跳过记录/输出/计数-1-less。

Aacini的原始解决方案是有效的,但是,它最多只能支持31个可能的值,因为FOR/F不能读取超过31个标记编辑-他的第二个解决方案消除了这一限制

下面是一个类似的概念,它对列表中的数字使用恒定宽度。这使我能够轻松地使用子字符串操作来提取随机选择的值,并从列表中删除每个值

如前所述,此解决方案支持从0到9999的值,最大可能值数=0
::RND_MAX虽然你们发布了非常好的解决方案,但我不忍心放弃,因此我不得不自己尝试,在问题中找到更好的代码;我们走吧

基本思想是生成一个由两列组成的表,其中第一列包含重复的随机数,第二列包含构成所有可能随机数的索引。下一步是按第一列对该表进行排序。最后,您需要从第二列中根据需要从任意多行中选取值(在我的方法中,我使用最后一行)。所有检索到的数字都是唯一的,因为第二列不包含任何重复项。这里是一个例子,假设我们需要
1,2,3,4,5,6,7,8,9,10
中的
8
随机数:

如您所见,没有返回重复的数字。这种方法的最大优点是,如果已经使用了相同的随机数,则不必重复计算随机数。缺点是需要创建完整的表,即使在可能的大量数字中只需要几个唯一的随机数

我不得不承认,这项技术不是我的主意,但不幸的是,我不知道它归功于谁


以下是我的代码,其中包含一些解释性说明:

@echo关闭
setlocal EnableExtensions EnableDelayedExpansion
rem随机数的总数,重复标志(`0`表示无重复):
设置/A“RND\U总计=8,标志\U DUP=0”
随机数的rem范围(最小、最大、间隔):
设置/A“RND_最小值=1,RND_最大值=10,RND_中间值=1”
rem将类似表格的数据写入临时文件:
>“%~n0.tmp”(
rem循环通过所有可能的随机数:
对于/L%%I in(%RND\U最小值%、%RND\U中间值%、%RND\U最大值%)执行(
rem计算一个随机数:
设置/A“随机数=!随机数!%((随机数最大值-随机数最小值)/随机数中间值+1)*随机数中间值+随机数最小值”
如果%FLAG\u DUP%eq 0(
rem重复被拒绝,因此以随机数作为第一列构建行:
echo!RND_NUM!,%%I
)否则(
允许rem重复,因此使用循环计数器作为第一列生成行:
回声%%I,!RND_NUM!
)
)
)
rem确定需要跳过表中的多少项才能获得总数:
设置/A“跳过=(RND\U最大值-RND\U最小值)/RND\U中间值+1,跳过-=RND\U总计”
如果%跳过%GTR 0(
@echo off
setlocal EnableDelayedExpansion

rem total number of random numbers:
set /A "RND_TOTAL=8"
rem range for random numbers (minimum, maximum, interval):
set /A "RND_MIN=1, RND_MAX=10, RND_INTER=1"

rem Create an input vector with all numbers in given range
set "n=0"
for /L %%i in (%RND_MIN%,%RND_INTER%,%RND_MAX%) do (
   set /A n+=1
   set "in[!n!]=%%i"
)
echo Input:
set in[
echo/

rem Extract RND_TOTAL elements from input vector in random order
for /L %%i in (1,1,%RND_TOTAL%) do (
   set /A "randIndex=(!random!*n)/32768+1"
   set /A "out[%%i]=in[!randIndex!], in[!randIndex!]=in[!n!], n-=1"
)
echo Output:
set out[
for /L %%I in (1,1,%RND_TOTAL%) do (
    rem compute a random number:
    set /A "RND_NUM[%%I]=!RANDOM!%%((RND_MAX-RND_MIN)/RND_INTER+1)*RND_INTER+RND_MIN"
    echo !RND_NUM[%%I]!
)
for /L %%I in (%rnd_min%,1,%RND_max%) do set "rnd_num{%%I}="
set /a rnd_count=rnd_total
:rnd_loop
rem compute a random number:
set /A "RND_selection=%RANDOM%%%((RND_MAX-RND_MIN)/RND_INTER+1)*RND_INTER+RND_MIN"
if not defined rnd_num{%rnd_selection%} (
 SET "rnd_num{%rnd_selection%}=Y"
 set /a rnd_num[%count%]=rnd_selection
 echo %rnd_selection%
 set /a rnd_count-=1
)
if %rnd_count% neq 0 goto rnd_loop
@echo off
setlocal enableDelayedExpansion

:: This script has the following limitations:
::   RND_MIN >= 0
::   RND_MAX <= 9999
::   ((RND_MAX - RND_MIN + 1) / RND_INTER) <= 1354
::
set "RND_MIN=1"
set "RND_MAX=10"
set "RND_INTER=1"
set "RND_TOTAL=8"

set /a cnt=(RND_MAX - RND_MIN + 1) / RND_INTER

:: Define a string containing a space delimited list of all possible values,
:: with each value having 10000 added
set "pool="
set /a "beg=RND_MIN+10000, end=RND_MAX+10000, cnt=(RND_MAX-RND_MIN+1)/RND_INTER"
for /l %%N in (%beg% %RND_INTER% %end%) do set "pool=!pool!%%N "

:: Build the randomly sequenced array of numbers
for /l %%N in (1 1 %RND_TOTAL%) do (

  %= Randomly select a value from the pool of all possible values  =%
  %= and compute the index within the string, as well as the index =%
  %= of the next value                                             =%
  set /a "loc=(!random!%%cnt)*6, next=loc+6"

  %= Transfer the index values to FOR variables =%
  for %%A in (!loc!) do for %%B in (!next!) do (

    %= Assign the selected value to the output array =%
    set /a "RND_NUM[%%N]=!pool:~%%A,5!-10000"

    %= Remove the value from the pool =%
    set "pool=!pool:~0,%%A!!pool:~%%B!"
    set /a cnt-=1

  )
)

:: Display the results
for /l %%N in (1 1 %RND_TOTAL%) do echo !RND_NUM[%%N]!
generated   sorted      returned
table       table       numbers
--------------------------------
1,1         1,1         -
9,2         1,6         -
8,3         10,8        8
3,4         3,4         4
9,5         4,7         7
1,6         5,9         9
4,7         8,10        10
10,8        8,3         3
5,9         9,2         2
8,10        9,5         5
generated           sorted              returned
2D-array            2D-array            numbers
------------------------------------------------
RND_NUM[1,1]        RND_NUM[1,1]        -
RND_NUM[9,2]        RND_NUM[1,6]        -
RND_NUM[8,3]        RND_NUM[10,8]       8
RND_NUM[3,4]        RND_NUM[3,4]        4
RND_NUM[9,5]        RND_NUM[4,7]        7
RND_NUM[1,6]        RND_NUM[5,9]        9
RND_NUM[4,7]        RND_NUM[8,10]       10
RND_NUM[10,8]       RND_NUM[8,3]        3
RND_NUM[5,9]        RND_NUM[9,2]        2
RND_NUM[8,10]       RND_NUM[9,5]        5
@echo off
setlocal    
setlocal enabledelayedexpansion

:: Populate array with all target numbers
for /L %%I in (%rnd_min%,1,%RND_max%) do (
        set /a idx="%%I"
        set "array[!idx!]=%%I
)

set /A I_NEED_SO_MANY_RANDOM_VALUES=%RND_max%-%rnd_min%
set /A TOTAL_UNUSED==%RND_max%-%rnd_min%

for /L %%I in (0,1,%I_NEED_SO_MANY_RANDOM_VALUES%) do (
    SET /A next_pick_index=!RANDOM! %% !TOTAL_UNUSED! 
    SET /A TOTAL_UNUSED-=1

    for /f "tokens=* delims= " %%a in ('echo !TOTAL_UNUSED!') do set unused_random=!array[%%a]!

    ::pick random value from initial array
    for /f "tokens=* delims= " %%a in ('echo !next_pick_index!') do set next_random=!array[%%a]!

    :: swap picked random value with the last not-used element in array
    SET "array[!next_pick_index!]=!unused_random!"

    :: put picked random value into new array
    SET "randomized_array[%%I]=!next_random!"
)

::output array to console
SET randomized_array[