I'm going through the Programming With a Purpose course on Coursera, and trying to come up with my own implementation of some of the example programs before looking at the example. I just finished the example of a gambling situation. I was hoping to get some more eyes on my code and be told whether my version is going to behave differently from the example. Due to the nature of simulations, I can't just compare the output of the two, since it will vary.
This is the explanation of what the code is meant to represent:
Gambler (PROGRAM 1.3.8) is a simulation that can help answer these ques tions. It does a sequence of trials, using Math.random() to simulate the sequence of bets, continuing until the gambler is broke or the goal is reached, and keeping track of the number of wins and the number of bets.
Mine:
public class Gambler
{
public static void main(String[] args)
{
int stake = Integer.parseInt(args[0]);
int initialStake = stake;
int goal = Integer.parseInt(args[1]);
int desiredGain = goal - stake;
int trials = Integer.parseInt(args[2]);
int games = 0;
int wins = 0;
int bets = 0;
while (games < trials)
{
bets++;
if (Math.random() >= 0.5)
{
stake++;
desiredGain = goal - stake;
if (desiredGain <= 0)
{
wins++;
games++;
stake = initialStake;
}
}
else
{
stake--;
if (stake < 1)
{
games++;
stake = initialStake;
}
}
}
int averageBets = bets / trials;
int percentWins = (100*wins / trials);
System.out.println("Theoretical chance of winning: " + 100*initialStake/goal);
System.out.println("Expected Bets: " + initialStake*(goal - initialStake));
System.out.println(percentWins + "% wins");
System.out.println("Average # bets: " + averageBets);
}
}
Example:
public class Gambler2
{
public static void main(String[] args)
{
int stake = Integer.parseInt(args[0]);
int goal = Integer.parseInt(args[1]);
int T = Integer.parseInt(args[2]);
int bets = 0;
int wins = 0;
for (int t=0; t<T; t++)
{
int cash = stake;
while (cash > 0 && cash < goal)
{
bets++;
if (Math.random() < 0.5) cash++;
else cash--;
}
if (cash == goal) wins++;
}
int averageBets = bets / T;
int percentWins = (100*wins / T);
System.out.println("Theoretical chance of winning: " + 100*stake/goal);
System.out.println("Expected Bets: " + stake*(goal - stake));
System.out.println(percentWins + "% wins");
System.out.println("Average # bets: " + averageBets);
}
}
The example is obviously much cleaner, but here is why I think mine should work the same:
- Each time the player's current stake exceeds the goal or reaches 0, the number of games played is incremented. When games == trials, the loop ends.
- When a game ends, stake is reset to its initial value for the next trial
- Bets is incremented each time the loop runs
- If the current stake meets or exceeds the goal, wins is incremented
I set up my conditions for interpreting the output of Math.random() opposite of the example. If that even makes a difference at all, it seems like it would take a lot more than 1000 trials before it became apparent.
I just want to make sure I'm not missing something.
TL;DR: You're getting different results for negative or otherwise invalid/unexpected input arguments.
A non-equivalence appears when passing a negative value as arg0 or arg1, which will always result in
averageBets == 0
in the example, but >=0 with your code. The reason for this happening basically hides in your conditional incrementing ofgames
, which allows for unintended "extra loops" if your conditionsdesiredGain <= 0
orstake < 1
are not met.Also, the condition
desiredGain = goal - stake; (desiredGain <= 0)
⬄(goal - stake <= 0)
⬄(goal <= stake)
in your "win branch" is not equivalent tocash < goal
, returning different results for allarg0 >= arg1
.The simpler example implementation handles these edge cases implicitly with
cash > 0
andcash < goal
, skipping the main work, but still running exactly all tries. It'd be fine, and actually preferable, IMHO, to add explicit input validation to your code to basically abort early, if the input values are out of bounds, but there's always a case to be made for keeping loops and conditions as simple, local, and immutable as possible, to avoid creating sneaky edge cases.Unfortunately, it is very common for many examples to lack error checking and/or input validation altogether, just like here.
This is amazing, thank you! You've given me a lot to think about, not just with respect to this program, but what sort of things to consider when analyzing the behavior of any program. Describing the classes of input like you did was enlightening.