Today I had a kind of enlightenment (after spending a whole school lesson ignoring the teacher and scribbling incomprehensible-to-anyone-but-me notes - I heard they call this a "fey mood"), and wrote a whole new algorithm in Ruby. It now runs in 8 seconds for 50d50 on my computer.
def diceCalc(number, sides) current = Hash.new current[number] = 1 number.times do |iteration| new = Hash.new new.default = 0 current.each_pair do |key, value| 0.upto(sides - 1) { |i| new[key + i] = new[key + i] + value } end current = new end return current end
This doesn't include the full functionality of my Java implementation (GUI, full analysis output to file), it just calculates and returns it in a Hash mapping ints (rollable value) to ints (# of occurences) But it's still so much more awesome.
Basically, the idea came from data trees and one of the hunches was how J (a pretty cryptic language I dabbled in a few months back) displays multidimensional arrays. Steven Wolfram's "A New Kind Of Science" did help too (he, among other things, writes about substitution systems there). This one is... heavily? optimized, though, so it's not really clear what is happening (unless you spend some time on it).
Let's assume x dice y sides each (xdy). We start with this here (practically zero-dimensional) array: [x]. For every xdy, x is the minimal obtainable value; you cannot roll 2 with 3d4. What I'm doing with the algorithm is, at every step I'm replacing every element z in the array with [z, z+1, z+2, z+3... z+y-1], so for 3d4 it would look like this: [3] [3, 4, 5, 6] - effectively 1d4 + 2 [[3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]] - effectively 2d4 + 1 [[[3, 4, 5, 6], [4, 5, 6, 7], [5, 6, 7, 8], [6, 7, 8, 9]], [[4, 5, 6, 7], [5, 6, 7, 8], ...]...] - effectively 3d4
This effectively (yes, I overuse that word) simulates a data tree. Now [amount of occurences] / [y ^ x] is the probability of that roll occuring.
This concludes the core algorithm.
I thought it would be a pretty memory-eating algorithm, after all it would store millions of values for higher input (which inevitably is the first thing everyone tries out...), so I used a hashtable storing the amount occurences of each value, so the maximum amount of values stored for xdy is (2 * (x - 1) * y) - or something like that (ie. 4900 for 50d50 - two values for each number between 50 and 2500 both inclusive). What happens for 3d4:
{3 => 1} {3 => 1, 4 => 1, 5 => 1, 6 => 1} {3 => 1, 4 => 2, 5 => 3, 6 => 4, 7 => 3, 8 => 2, 9 => 1} {3 => 1, 4 => 3, 5 => 6, 6 => 10, 7 => 12, 8 => 12, 9 => 10, 10 => 6, 11 => 3, 12 => 1} (equivalent of what I wrote a few lines earlier in the form of arrays - see? Takes much less numbers to store).
This approach is probably slightly slower, but this doesn't make much of a difference for smaller input, and EXTREMELY cuts the memory cost for larger input. Also, it makes post-processing (ie. actually using the data) easier, because it already is stored in a usable form. Also, this being Ruby, which allows numbers beyond Int.MAX (although it calculates these slower) the algorithm is much more graceful.
I'm pretty happy now.
|