Michdd Posted November 16, 2008 Share Posted November 16, 2008 I've currently been using something like this: $array = array(); $monster[1] = "spirit"; $times[1] = "18"; $monster[2] = "golden spirit"; $times[2] = "18"; $monster[3] = "hank"; $times[3] = "15"; $monster[4] = "brown fluffy"; $times[4] = "40"; $monster[5] = "dralmont"; $times[5] = "5"; foreach ($monster as $index => $value) { $array = array_merge($array, array_fill(0, $times[$index], $value)); } srand((float) microtime() * 10000000); $input = $array; $rand_keys = array_rand($input, 3); $monster = $input[$rand_keys[0]]; Which is supposed to give certain things a higher probability of being picked from the array, because some times are placed more than other in there. However I tested it, and it doesn't seem to work very good at all. I set one of the options to like 40000000000 yet I tested it 5 times, and that option didn't even come up. When the odds should've made it come up. So I'm guessing there has to be a better way where I can generate a random result, but still control the probability somewhat. Any help? Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/ Share on other sites More sharing options...
genericnumber1 Posted November 16, 2008 Share Posted November 16, 2008 your way is very inefficient... not that I can think of a way of doing it any better. <?php $array = array(); $monster[1] = "spirit"; $times[1] = 18; $monster[2] = "golden spirit"; $times[2] = 18; $monster[3] = "hank"; $times[3] = 15; $monster[4] = "brown fluffy"; $times[4] = 40; $monster[5] = "dralmont"; $times[5] = 5; foreach ($monster as $index => $value) { $array = array_merge($array, array_fill(0, $times[$index], $value)); } $monster = $array[array_rand($array)]; echo $monster; ?> is a tad more efficient and worked for me, but it still is incredibly inefficient. I'm not well versed enough in randomization to provide you a better solution, sorry. edit: wait, nvm, I think I may have thought of something simple Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691133 Share on other sites More sharing options...
genericnumber1 Posted November 16, 2008 Share Posted November 16, 2008 How about this? <?php $monster[1] = "spirit"; $times[1] = 18; $monster[2] = "golden spirit"; $times[2] = 18; $monster[3] = "hank"; $times[3] = 15; $monster[4] = "brown fluffy"; $times[4] = 40; $monster[5] = "dralmont"; $times[5] = 5; $rand = mt_rand(1, array_sum($times)); $timeSum = 0; foreach($times as $key => $value) { $timeSum += $value; if($rand <= $timeSum) { $monsterName = $monster[$key]; break; } } echo $monsterName."\n"; ?> There might be a simpler method than this, but it basically assigns a range of numbers for each monster and generates a number inside the range min (1) and range max (the sum of all the times) it will then check to see which monster's range the number falls in. It cuts out that ridiculous overhead of loading a huge array with multiple values hundreds of times. Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691135 Share on other sites More sharing options...
corbin Posted November 16, 2008 Share Posted November 16, 2008 lol wow genericnumber1 we came up with the same solution (you beat me by far hehe.... I should refresh more often, or not get distracted....) I thought it was ridiculous to array_fill too. <?php $a = "abcdefghij"; $a = str_split($a); $p = array( 10, 10, 10, 10, 10, 10, 10, 10, 10, 10 ); $return = array(); foreach($a as $val) { $return[$val] = 0; } for($i = 0; $i < 1000000; ++$i) { $return[array_get_rand($a, $p)]++; } print_r($return); function array_get_rand($a, $p) { if(($c = count($a)) != count($p)) return false; srand(microtime(true)); $max = array_sum($p); $r = mt_rand(0, $max); $x = 0; for($i = 0; $i < $c; ++$i) { $x += $p[$i]; if($r <= $x) { return $a[$i]; } } } Could easily be adapted to this situation. By the way Michdd, array indexes start with 0, so unless you only posted part of the code, why not start with 0? Better yet, why not just do $a[] = "blah"; $a[] = "bleh" so on? Also, why are you quoting numbers? That's kind of weird if you're planning on using them as numbers. Also, I think it's a notice to set a key in a variable that hasn't been previously declared an array. Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691137 Share on other sites More sharing options...
flyhoney Posted November 16, 2008 Share Posted November 16, 2008 This is a fun problem so I am throwing my 2 cents into the discussion. This is what I have used for the weighted random problem in the past. I think I ported it from a PERL solution I ran across at some point, but I can't completely remember. Would be cool to see someone profile the solutions we have listed so far. I think this solution is O(n). This is the function: <?php function wrand($data) { $total = array_sum($data); foreach ($data as $key => $weight) { $dist[$key] = $weight/$total; } $rand = lcg_value(); foreach ($dist as $key => $weight) { if (($rand -= $weight) < 0) { return $key; } } } ?> And this is how you would use it: <?php $dinos['t rex'] = 1; $dinos['pterodactyl'] = 2; $dinos['raptor'] = 5; $dinos['dennis nedry'] = 10; $best_thing_about_jurassic_park = wrand($dinos); ?> And here are some stats after running wrand() 10,000 times: probability expected pterodactyl 10.89% 11.1% dennis nedry 55.79% 55.6% raptor 28.16% 27.8% t rex 5.15% 5.6% 0.030737 ms avg. runtime * edit * I think I jacked this code from this perl post of someone who jacked the code from a perl book: http://www.perlmonks.com/index.pl?node_id=158682 Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691167 Share on other sites More sharing options...
laffin Posted November 16, 2008 Share Posted November 16, 2008 I use a similar system, this is wut i got <?php $monsters= array( 'spirit' => 18, 'golden spirit'=>18, 'hank'=>15, 'brown fluffy'=>40, 'dralmont'=>5 ); // First add the weights $tweight=array_sum($monsters); //Create a table of roll weights $atpercent=0; foreach ($monsters as $key=>$weight) { $ptable[$key]=round(($atpercent*100),2); $atpercent+=($weight/$tweight); } // Reverse are table so the highest weights get compared first $ptable=array_reverse($ptable); // Ahhh, now ya almost get it // Now random rolls // just a sample array to hold our counters foreach ($monsters as $key=>$weight) { $rolldata[$key]=0; } $numrolls=5000; for($i=0;$i<$numrolls;$i++) { $roll=rand(0,100); foreach ($ptable as $key=>$percent) if($roll>$percent) {$rolldata[$key]++; break;} } //show stats foreach($rolldata as $key=>$data) echo $key .' = ('.$monsters[$key]/$tweight .'%) '. ($data/$numrolls)."%\r\n"; Results are predicatable according to the weights spirit = (0.1875%) 0.18% golden spirit = (0.1875%) 0.1904% hank = (0.15625%) 0.1556% brown fluffy = (0.41666666666667%) 0.41% dralmont = (0.052083333333333%) 0.053% Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691177 Share on other sites More sharing options...
flyhoney Posted November 16, 2008 Share Posted November 16, 2008 Same as my previous post but wrapped in a nice little class. <?php class WeightedRandom { protected $distribution = array(); function __construct($weights) { $total = array_sum($weights); foreach ($weights as $key => $weight) { $this->distribution[$key] = $weight/$total; } } function rand() { $rand = lcg_value(); foreach ($this->distribution as $key => $weight) { if (($rand -= $weight) < 0) { return $key; } } } } $weights = array( 't rex' => 1, 'pterodactyl' => 2, 'raptor' => 5, 'dennis nedry' => 10 ); $weights = new WeightedRandom($weights); echo $weights->rand(); ?> Quote Link to comment https://forums.phpfreaks.com/topic/132911-getting-a-random-result-but-with-probabilties/#findComment-691188 Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.