Jump to content

Probility in PHP


Hall of Famer

Recommended Posts

Well I was trying to design a random encounter event system, but got myself into a big issue with PHP random number generator(rand or mt_rand). A simple code for how these functions work is:

 

$num = mt_rand(0,15)
if($num < 5){
// codes to execute
}
else{
// codes to execute
}

 

The codes look nice, but it becomes a problem when multiple events with certain probability to occur need to be programmed. Consider there are 5 colors to select for a new automobile(black, while, yellow, red and blue), the probability for each of the color to be chosen is (30%, 30%, 20%, 10%, 10%), how am I supposed to write such a script in PHP efficiently? I know its possible to do it like this, but...

$num = mt_rand(0,99)
if($num < 30){
$color = "black";
}
else if($num >= 30 and $num <60 ){
$color = "white";
}
else if($num >= 60 and $num <80 ){
$color = "yellow";
}
else if($num >= 80 and $num <90 ){
$color = "red";
}
else{
$color = "blue";
}

 

See the problem with this script? Yes, it is long and tedious, and more importantly inflexible. For a PHP random encounter script, the user may not know how many random events can occur(except for the fact that their probability values do add up to 1). It is also a common practice to retrieve random event names, probabilities and other properties from mysql with mysql_fetch_array() and loop. I do not know how to write such a program at this moment, can anyone of you please give a try and lemme know there is a way to handle multiple random events with different probability to occur easily? Thanks.

 

Link to comment
Share on other sites

Here's a object-oriented solution.

 

If you're confused and would like comments, I'll be happy to add them

 

<?php 

$color = new probability();

$color->addEvent( 'black', 30 );
$color->addEvent( 'white', 30 );
$color->addEvent( 'yellow', 20 );
$color->addEvent( 'red', 10 );
$color->addEvent( 'blue', 10 );

echo 'Single random event: '.$color->randomEvent(1).'<br>';

echo 'Multiple random events: '. implode(', ', $color->randomEvent(5));

class probability {

private $events = array();

public function addEvent( $value, $probability ) {
		$this->events[$value] = $probability;
}

public function removeEvent( $value ) {
	if( array_key_exists($value, $this->events) )
		unset( $this->events[$value] );
}

public function randomEvent( $num ) {
	$events = array_reverse( $this->buildTable(), TRUE );
	$sum = array_sum($this->events);
	if( $num == 1 ) {
		$rand = mt_rand( 1, $sum );
		foreach( $events as $key => $event )
			if( $event <= $rand ) return $key;
	} else {
		$return = array();
		for( $i = 0; $i < $num; $i++ ) {
			$rand = mt_rand( 1, $sum );
			foreach( $events as $key => $event )
				if( $event <= $rand ) {
					$return[] = $key; break;
				}
		}
		return $return;
	}
}

private function buildTable() {
	$events = $this->events;
	$total = 0;
	foreach( $events as &$event ) {
		$prev = $event;
		$event = $total;
		$total += $prev;
	}
	return $events;
}

}

?>

Link to comment
Share on other sites

Thanks a lot, this looks like a powerful script indeed. Would you mind adding a few comments in your code explaining how a random number is generated and how the script determines if the condition matches for a certain instance(such as black or white color)? Also what if the data for object's name and probability properties are retrieved from a mysql table's two columns called 'name', 'probability'? I am fine with OOP, but I dunno how to write OOP code together with Mysql integration.

Link to comment
Share on other sites

Do you understand general class structure?

 

If they're retrieved from MySQL, I'd suggest simply using a while loop to add them to the class, like this

 

<?php 

$sql = new mysqli('localhost','root','','db');
$prob = new probability();
$query = 'SELECT `name`, `probability` FROM `probability';
if( ($result = $sql->query($query)) === FALSE )
echo 'Query error';
else {
while( $row = $result->fetch_assoc() )
	$prob->addEvent( $row['name'], $row['probability'] );
$result->free();
}

echo '2 random colors: ' . implode(', ', $prob->randomEvent(2));

class probability {

// This will hold our possible outcomes along with thier probabilities.
// I store them with the key being the name of teh event, and the value
// it's probability to show up. $this->events['name'] = probability
private $events = array();

// This function will add a new event to the array. I didn't include a check
// to make sure a duplicate exists, so as it is, if you call
// $this->addEvent( 'blue', 20 );
// followed by
// $this->addEvent( 'blue', 30 );
// 'blue' will now have a value of 30.
public function addEvent( $value, $probability ) {
	$this->events[$value] = $probability;
}

// Simple function to remove an event. I don't think the array_key_exists()
// check is needed, but it's there.
public function removeEvent( $value ) {
	if( array_key_exists($value, $this->events) )
		unset( $this->events[$value] );
}

// Here's the meaty part. I'll comment this line-by-line
public function randomEvent( $num = 1 ) {
	// Generate a reverse list of events, using our class method below to change
	// the individual probabilities into comparable numbers.
	$events = array_reverse( $this->buildTable(), TRUE );
	// This is the total sum of all the probabilities. This gives us our max range
	// when we generate our random number
	$sum = array_sum($this->events);
	// If only 1 result is needed, we want to return a string
	if( $num == 1 ) {
		// Generate the random number between 1 and the sum of all probabilities
		$rand = mt_rand( 1, $sum );
		// Loop through probabilities, greatest to lowest.
		foreach( $events as $event => $probability )
			// If the probability is less than or equal to our random number,
			// it must be the value we want, because we go highest to lowest.
			// By returning the value, the function ceases to execute.
			if( $probability <= $rand ) return $event;
	// If more than 1 result is needed, we'll build an array.
	} else {
		// Set up an empty array to hold the values.
		$return = array();
		// Loop through the following code $num times
		for( $i = 0; $i < $num; $i++ ) {
			// Generate our random number INSIDE the loop, so it gets changed
			// every time
			$rand = mt_rand( 1, $sum );
			// Again, loop through each event
			foreach( $events as $event => $probability )
				// Again, check if it's less than or equal to the value we want
				if( $probability <= $rand ) {
					// If so, add a new array entry. Since we don't need to loop
					// through any more events until we generate a new random number,
					// we use break to escape out of the foreach.
					$return[] = $event; break;
				}
		}
		// Return the array generated
		return $return;
	}
}

// Here's where we modify the events array to become more useable. We do this on the
// fly, rather than when we add an event because it makes removing events a much easier
// process. This method will add each probability to the sum of the ones before it. I
// will show you below why I chose to do this.
private function buildTable() {
	// Create a local copy of the events array. We can change this all we want without
	// modifying data that other class methods might still need.
	$events = $this->events;
	// This will hold the running total of the probabilities
	$total = 0;
	// Loop through each probability. The &$event will apply any changes made in the
	// loop to the $events array.
	foreach( $events as &$probability ) {
		// Hold the original probability in a temporary array
		$original = $probability;
		// Set the probability to the runnig total of all probabilities before it
		$probability = $total;
		// Add the oringial probability to the running total
		$total += $original;
	}
	// Return the momdified array
	return $events;
}
/* To give you a visual of what this function does... here's a print_r() of the array
before and after the foreach loop

BEFORE
	Array ( [black] => 30 [white] => 30 [yellow] => 20 [red] => 10 [blue] => 10 )
AFTER
	Array ( [black] => 0 [white] => 30 [yellow] => 60 [red] => 80 [blue] => 90 )

As you can see, the sum of the BEFORE array is 100, so the randomEvent method will
generate a random number between 1 and 100 (say, 36), flip the AFTER array, and go
through it,	top to bottom, until it finds a value less than or equal to 36.
Blue is 90, so it skips. Red is 80, so it skips. Yellow, 60, skip. White is 30, which
is less than 36, so return it! */
}

?>

 

SQL Dump used

 

CREATE TABLE IF NOT EXISTS `probability` (
  `name` varchar(10) NOT NULL,
  `probability` int(2) NOT NULL
) ENGINE=MyISAM DEFAULT CHARSET=latin1;

INSERT INTO `probability` (`name`, `probability`) VALUES
('black', 30),
('white', 30),
('yellow', 20),
('red', 10),
('blue', 10);

 

Hope this helps. If you want me to get into more detail anywhere, let me know.

Link to comment
Share on other sites

Glad I could help. Please mark as solved.

 

Integrating MySQL with objects is no different than integrating MySQL with any other part of PHP. You simply build your methods to accept values that your MySQL class of choice will return.

 

In this case, I simply used PHP's mysqli class to turn my results into arrays, which my method was more than happy to accept.

Link to comment
Share on other sites

I see, thanks again. I wonder if the class mysqli can identify a table's prefix though, as I browsed through the manual of mysqli class from php.net. It seems that the four arguments passed into mysqli class are 'hostname', 'username', 'password', 'database', what should I do with database tables with a prefix?

Link to comment
Share on other sites

Using the class is very similar to using it in procedural style. There are a few slight differences.

 

Procedural

<?php 

$table_prefix = 'blah_';

$link = mysqli_connect('localhost','root','','db');
$query = 'SELECT `name`, `probability` FROM `'.$table_prefix.'probability`';
if( ($result = mysqli_query($link, $query)) === FALSE )
echo 'Query error';
else {
if( mysqli_num_rows($result) < 1 )
	echo 'No rows found';
else {
	while( $row = mysqli_fetch_assoc($result) )
		echo $row['name'].' - '.$row['probability'].'<br>';
}
}

?>

 

Object-Oriented

 

<?php 

$table_prefix = 'blah_';

$link = new mysqli( 'localhost', 'root', '', 'db' );
$query = 'SELECT `name`, `probability` FROM `'.$table_prefix.'probability`';
if( ($result = $link->query($query)) === FALSE )
echo 'Query error';
else {
if( $result->num_rows < 1 )
	echo 'No rows found';
else {
	while( $row = $result->fetch_assoc() )
		echo $row['name'].' - '.$row['probability'].'<br>';
}
}

?>

 

If you want to automate the process, you'll have to design your own class that extends the MySQLi class.

Link to comment
Share on other sites

I see, that makes sense. I should write an inheritance class like this then?

 

Class mysqli_extra extends mysqli{
  var $prefix;
// more lines of codes in this child class, the prefix name can be retrieved from config file.
}

 

Also a question that seems irrelevant to this topic. Why dont you use mysql_fetch_object in the OOP approach? Is it because there is no easy way to iterate an object like what we can normally do with an array(by using foreach).

 

Link to comment
Share on other sites

Arrays still have their place in OOP. I usually use fetch_assoc because I don't need an object to simply display the data. If, on the other hand, you have a group of functions that you commonly apply to data retrieved from the database, then using fetch_object with the 'class_name' argument is a great idea.

 

The method you suggested is fine. Personally, I have a database class that uses an instance of the MySQLi class, rather than extending it. This is mostly because I use an interface for my database classes, so I have a common format regardless of the database used. I've found simply creating an instance of the existing object easier

 

Quick example of what I mean

class database {

private $sql, $prefix, $debug = FALSE;

public function __construct( $host, $user, $pass, $db, $prefix ) {
	$this->sql = new mysqli($host,$user,$pass,$db);
	$this->prefix = $prefix;
}

public function select( $table, array $columns, $where = FALSE, $limit = FALSE, $order = FALSE ) {
	$query = 'SELECT `' . implode('`,`',$columns) .
		' FROM `'.$this->prefix.$table.'`' .
		( $where ? ' WHERE '.$where : '' ) .
		( $limit ? ' LIMIT '.$limit : '' ) .
		( $order ? ' ORDER BY '.$order : '' );
	if( $this->debug ) echo $query;
	if( ($r = $this->sql->query($query)) === FALSE ) return FALSE;
	elseif ( $r->num_rows < 1 ) return array();
	else {
		$return = array();
		while( $d = $r->fetch_assoc ) $return[] = $d;
		return $return;
	}
}

....

Link to comment
Share on other sites

I see, this makes perfect sense to me. It seems that you have a select function in it, and it is quite flexible to use. Do you also have update and delete functions for the class database? From my experience, its quite annoyingly tedious to write multiple lines of mysql query commands in a PHP codes.

Link to comment
Share on other sites

Yes, I have updates, deletes, and complex selects like nested selects and joins.

 

Generally, these can change form script to script slightly, as sometimes it's hard to write a single method that can handle 4 joins, and redundant to have that kind of method in a simple script. The key is being able to port the script to any supported database without changing any code beyond a config variable that sets it.

 

It's getting pretty abstract, but it's something to think about as you progress. I doubt you'll need anything like this until you start building larger projects.

Link to comment
Share on other sites

Well actually I do need a more efficient and convenient way to select/insert/update database info. Some of my script files has tens of lines of "SELECT from db.tablename where columname = '$name'". It is getting quite tedious and annoying, it would be nice if you could provide me with an easier approach with or w/o OOP.

Link to comment
Share on other sites

Well here are some examples:

 

$query = "SELECT * FROM {$prefix}items WHERE itemname = '{$itemname}'";
$result = mysql_query($query);
$row = mysql_fetch_array($result);

 

The above code uses three lines to retrieve database info from a table and sent it to an array called $row for future use. It may look fine with only one query being run, but it can be rather annoying if I have to write 2 or 3+ times of such code over and over again, especially in the script file of admin control panel. Similarly, the one below updates database info, and I believe it can be simplified.

 

mysql_query("UPDATE {$prefix}items SET category='{$category}' , 
                                                               itemname='{$itemname}' , 
						       shop='{$shop}' , 
						       price='{$price}' ,                   
						       consumable='{$consumable}' WHERE id='{$id}'");		

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.