-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.

7
  • why does this need to be a batch file? Commented Sep 5, 2015 at 20:08
  • Because I can only program in batch. Commented Sep 5, 2015 at 20:11
  • post what you have tried, your code & the lines that don't work. SO is not a write-my-software website. Commented Sep 5, 2015 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 (!) Commented Sep 5, 2015 at 20:12
  • 1
    possible duplicate of How to use a batch file to delete a line of text in a bunch of text files? Commented Sep 5, 2015 at 20:20

6 Answers 6

2

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Simple and very elegant.
2

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

Comments

1

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++;
  }

Comments

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!"

4 Comments

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, 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.
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
@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 algorithm.
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"

3 Comments

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...
"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".
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)...
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.