-2

I need a way to use batch to look at every line of a text file, and delete half of the lines in that text file, choosing which lines to delete at random.

This is to simulate a tournament within a game of D&D. All I need is a way to crunch out the winners of each tourney round. I can easily make a batch file that copies the text file and renames it for each round, but it's the reduce by half part that I'm not good with.

Edit: I want a batch file to do this because it would take way to much time to do by hand at the table. Also the game's very old and roleplay focused, so there is an actual list of NPC characters in the tourney and the PCs will want to know how their friends fared in the competition.

  • why does this need to be a batch file? – Chris Beck Sep 05 '15 at 20:08
  • Because I can only program in batch. – Meep the Changeling Sep 05 '15 at 20:11
  • post what you have tried, your code & the lines that don't work. SO is not a write-my-software website. –  Sep 05 '15 at 20:11
  • @MeeptheChangeling: I'm not sure how easy it is to get random numbers in batch, it might be pretty awkward. wait actually I found a link (!) – Chris Beck Sep 05 '15 at 20:12
  • possible duplicate of [How to use random in BATCH script?](http://stackoverflow.com/questions/5777400/how-to-use-random-in-batch-script) – Chris Beck Sep 05 '15 at 20:14
  • 1
    possible duplicate of [How to use a batch file to delete a line of text in a bunch of text files?](http://stackoverflow.com/questions/19424728/how-to-use-a-batch-file-to-delete-a-line-of-text-in-a-bunch-of-text-files) – Ken White Sep 05 '15 at 20:20
  • You cannot randomly delete lines, otherwise you could end up with no winner or two winners from any given head to head competition. You must decide on a file layout that represents the tournament structure, and then for each round, exactly one random winner must be selected (preserved) from each head to head competition. – dbenham Sep 07 '15 at 11:32

6 Answers6

1
@echo off
    setlocal enableextensions disabledelayedexpansion

    set "inputFile=list.txt"
    set "outputFile=remaining.txt"

    set "odd=1"
    >"%outputFile%" (
        for /f "tokens=1,* delims=¬" %%a in ('
            "cmd /q /v /e /c" 
                for /f "usebackq delims=" %%l in ("%inputFile%"^) do (
                    set /a 100000000+%random%*^!random^!^&echo(¬%%l
                ^)
            ""
            ^| sort /+3
        ') do if not defined odd ( set "odd=1" ) else (
            echo %%b
            set "odd="
        )
    )
    type "%outputFile%"

This will take the input file, for each line echo its contents with a random prefix, sort this list using the random number as key and from this list echo only odd lines to output file.

edited I've seen the Aacini's answer and, yes, it can be useful to have the output in the same order than the input. If this is the case, just to have another version

@echo off
    setlocal enableextensions disabledelayedexpansion

    set "inputFile=list.txt"
    set "outputFile=remaining.txt"    

    setlocal enabledelayedexpansion

    rem Retrieve and calculate line limits to process
    for /f %%a in ('^<"!inputFile!" find /c /v ""') do set /a "nLines=%%a", "nLimit=%%a/2"

    rem Prepare an array with shuffled line numbers 
    for /l %%a in (1 1 %nlines%) do (
        set /a "sel=!random! %% %%a + 1"
        if !sel!==%%a ( set "r[%%a]=%%a" ) else (
            for %%s in (!sel!) do set /a "r[%%a]=!r[%%s]!", "r[%%s]=%%a"
        )
    )

    rem Read input file and output selected lines
    <"!inputFile!" >"!outputFile!" ( 
        for /l %%a in (1 1 %nLines%) do (
            set /p "line=" || set "line="
            if !r[%%a]! leq %nLimit% echo(!line!
        )
    ) 
    type "!outputFile!"
Community
  • 1
  • 1
MC ND
  • 69,615
  • 8
  • 84
  • 126
  • Your first solution has a potential problem that multiple runs within the same second will all produce the same result because the random number generator is seeded with the current time (to the nearest second) when CMD.EXE is launched. – dbenham Sep 07 '15 at 12:17
  • @dbenham, If the multiple instances are executed from the same `cmd` instance, there will be no problem. If you look at the `set /a 100000000+%random%*^!random^!` you will see the inner (command line context) `cmd` is using a random value from the parent (batch) instance. If each of the multiple started (parent, batch) instances is created in a separate `cmd` instance, then, of course, you are rigth. – MC ND Sep 07 '15 at 12:30
  • I tested, and observe that you are correct, but I can't wrap my head around your algorithm! Everyone's solution seems needlessly complex to me. Plus, the OP's design is flawed. You cannot randomly delete 50% of the contestants in a tournament. Instead, each game between 2 scheduled opponents must have exactly one winner. See [my answer](http://stackoverflow.com/a/32438269/1012053) – dbenham Sep 07 '15 at 12:56
  • @dbenham, without seeing the rules in the *tournament*, I can't say this approach is a flaw in a game with decisions conditionated by dices. In the first code de algorithm is the described one: prefix each line with a random number, sort on the random prefix, output odd lines skipping even ones. In the second code the algorithm is the "inside-out" [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#The_.22inside-out.22_algorithm) algorithm. – MC ND Sep 07 '15 at 14:06
1

This method preserve the order of original lines.

@echo off
setlocal EnableDelayedExpansion

rem Generate an array of line numbers with all file lines
for /F "delims=:" %%a in ('findstr /N "^" input.txt') do (
   set "n[%%a]=%%a"
   set "lines=%%a"
)

rem Delete half the numbers in the array in random order
set /A halfLines=lines/2
for /L %%n in (%lines%,-1,%halfLines%) do (
   set /A rnd=!random!*%%n/32768+1
   set /A n[!rnd!]=n[%%n]
   set "n[%%n]="
)

rem Reorder the resulting elements
for /F "tokens=2,3 delims=[]=" %%i in ('set n[') do (
   set "l[%%j]=1"
   set "n[%%i]="
)

rem Copy such lines
(for /F "tokens=1* delims=:" %%a in ('findstr /N "^" input.txt') do (
   if defined l[%%a] (
      echo(%%b
      set "l[%%a]="
   )
)) > output.txt
Aacini
  • 65,180
  • 12
  • 72
  • 108
1

The design specified in the question is flawed. You cannot randomly delete half the lines because then for any given game, you might end up with 2 winners, or no winners. The input file must have a structure that specifies which contestants play each other, and then a winner must be randomly selected from each contest.

The solution below assumes each line represents a player, and for each round, line 1 plays line 2, line 3 plays line 4, etc. The number of lines must always be a power of 2 >=2 (2, 4, 8, 16, 32, 64, ...)

@echo off
setlocal enableDelayedExpansion
set "file=tournament.txt"
for /f %%C in ('find /c /v "" ^<"%file%"') do (
  for /l %%N in (1 2 %%C) do (
    set /p "p1="
    set /p "p2="
    set /a "1/(!random!%%2)" 2>nul&&echo !p1!||echo !p2!
  )
) <"%file%" >"%file%.new"
move /y "%file%.new" "%file%" >nul

The outer loop counts the number of lines. The inner loop counts the odd numbers from 1 to the count, so it iterates exactly (line count divided by 2) times. The inner loop has input redirected to the source file, and the output redirected to a temporary file.

Each inner loop iteration represents a game in the tournament. SET /P is used to load the player names into variables. Then 1 is divided by a random number modulo 2, which will result in a divide by 0 error 50% of the time. The error message is redirected to nul, and then conditional operators are used to print one or the other player to the output file.

Upon completion of the loop, the temporary file is moved to replace the original file.

dbenham
  • 127,446
  • 28
  • 251
  • 390
0

This is a native Windows batch script using Jscript to randomise the lines of a text file and print half of them.

The size of the file is limited to the RAM that Jscript is able to request.

@if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment
:batch file portion
@echo off
if "%~1"=="" (
echo(Purpose: Randomises a text file - prints every 2nd random line
echo(
echo(Syntax: "%~0" "inputfile.txt" ^> "outputfile.txt"
echo(
pause
goto :EOF
)

:: Randomises a file
::
:: Reads the lines of file %1 into a jscript sparse array
:: with a random number and space before each line.
:: The array is sorted using the random numbers,
:: and the randomised lines are printed - prints every 2nd random line


@echo off
cscript //E:JScript //nologo "%~f0" %* 
:pause
exit /b


************ JScript portion ***********/

  e=0;
  var array = new Array();
  fso = new ActiveXObject("Scripting.FileSystemObject");
  input=fso.OpenTextFile((WScript.Arguments(0)),1);

  while (!input.AtEndOfStream) {
    array[e]=Math.random()+" "+input.ReadLine();
    e++;
  }
  input.close();

  array.sort();

  var re = new RegExp(".*? (.*)");

  c=0;
  while (c<e) {
    var arr = re.exec(array[c])
    if (c % 2) WScript.StdOut.Writeline(RegExp.$1);
    c++;
  }
foxidrive
  • 40,353
  • 10
  • 53
  • 68
0

Including a PowerShell solution for completeness.

$lines = Get-Content input.txt
$lines | foreach { $i=0 } {
    [PSCustomObject]@{index=$i;line=$_}
    $i++
} |
Get-Random -count ($lines.length / 2) |
sort index |
select -Expand line |
Set-Content output.txt

This reads the input file, and builds an array of custom objects associating the text with its line number. The script uses Get-Random to select half the lines. Get-Random does not preserve order, so the script then sorts on the original line number. It then extracts the original lines in order and writes them out.

This script above requires PowerShell 3.0 for the PSCustomObject. Version 3.0 comes preinstalled on Windows 7 and above. If you need to run on Vista, you can use PSObject instead, as shown in this answer or this blog post.

Note that if the line order doesn't matter, then this script becomes much simpler.

$lines = Get-Content input.txt
$lines | Get-Random -count ($lines.length / 2) | Set-Content output.txt

To run a PowerShell script from a batch file or the CMD prompt, use the following.

powershell.exe -ex bypass -file yourscript.ps1

Or if your script fits entirely in one line, no need to create a separate ps1

powershell.exe -c "$l = gc input.txt; $l | Get-Random -c ($l.length / 2) | sc output.txt"
Ryan Bemrose
  • 9,018
  • 1
  • 41
  • 54
  • Excuse me, I don't see the logic behind your "Including a PowerShell solution for completeness" comment. The question have the `windows` and `batch-file` tags only. Why a PowerShell solution makes the answers "complete"? Should we also include Phyton, Ruby, PHP, etc solutions "for completeness"? The other programming language that comes preinstalled on _all_ Windows versions is JScript (_not_ PowerShell), so including a JScript solution in a `batch-file` tagged question "for completeness" seems more logical than PS... – Aacini Sep 13 '15 at 03:24
  • "Completeness" in this case is judged by how much it solves the problem the user is having. In my experience, many people trying to build a "batch file" solution really just want a shell scripting solution in Windows. PowerShell is a superior Windows scripting language pre-installed on every (supported) OS that CMD runs on. It solves "I want to script Windows" better than CMD does, even when the user doesn't know what to type into search and just puts "batch". – Ryan Bemrose Sep 13 '15 at 04:19
  • Your last comment is based on _opinions_ only. I want not to start an _"in my opinion"_ comments war, but stay with verifiable facts only. I tried to test your programs, but the last two versions shown `Get-Random: Can not validate argument of 'InputObject'` error message _79 times_ (?). The first version run correctly, it takes an average of 2.02 seconds to process a file with 620 lines (19 KB) after 5 runs under Windows 8.1 64 bits. My Batch file solution takes 0.88 seconds under the same conditions, so PowerShell does _not_ solve (this) problems better than CMD (at least, not in speed)... – Aacini Sep 14 '15 at 12:08
0

One way to do this would be to create simple files for each character. Then use a random script to do something like this:

set/a number=%random% * (number of people there are) / 32768 + 1
set status=dead
call person%number%.bat   (Each file should contain a set status=live script)
if %status% == live (
set/a peopletokill=%peopletokill%-1
del person%number%.bat
)
if %peopletokill% == 0 (
exit
)

Something like that could work.