ITT #12: Display Random Entries with PHP

A trick for dealing with array elements

Using Array Handling Functions to Randomize Displays

I ran into an interesting problem while developing a new blogging system recently where I needed to generate a subset of array elements in random order.

A random subset of elements is useful for displaying a sampling of entries that changes with every page load, such as four random blog entries, a photo gallery that stays interesting even if new photos aren't uploaded, or, if you want to use a sweet content slider, you could show random portfolio or blog entry previews and keep your slider fresh.

Defining the Problem

The Array

First things first, we need to know what we're dealing with. For the purpose of this example, we'll be using a multi-dimensional array that looks something like this:


$entries = array(
	array(
		'title' => 'Entry One',
		'author' => 'Jason Lengstorf',
		'date' => 'April 8, 2009'
	),
	array(
		'title' => 'Entry Two',
		'author' => 'Jason Lengstorf',
		'date' => 'April 9, 2009'
	),
	array(
		'title' => 'Entry Three',
		'author' => 'Jason Lengstorf',
		'date' => 'April 10, 2009'
	),
	array(
		'title' => 'Entry Four',
		'author' => 'Jason Lengstorf',
		'date' => 'April 11, 2009'
	),
	array(
		'title' => 'Entry Five',
		'author' => 'Jason Lengstorf',
		'date' => 'April 12, 2009'
	),
	array(
		'title' => 'Entry Six',
		'author' => 'Jason Lengstorf',
		'date' => 'April 13, 2009'
	),
	array(
		'title' => 'Entry Seven',
		'author' => 'Jason Lengstorf',
		'date' => 'April 14, 2009'
	),
	array(
		'title' => 'Entry Eight',
		'author' => 'Jason Lengstorf',
		'date' => 'April 15, 2009'
	)
);

This is an array that's similar to the kind that would be returned from a database query. In it, we have eight entries, each of which contains another array that holds the title, author, and date of the entry (I omitted the entry itself in the interest of brevity).

The Goal

Our goal is to create a subset of four entries in random order, and to have the subset change every time the page is loaded (i.e. entries 4, 2, 8, and 7 are loaded the first time, then entries 3, 8, 1, and 6, and so on).

Writing the Code

Our array will be processed in a function called getRandomSubset(), which will accept two arguments: the array ($entries), and the number of entries to be returned in the subset ($number).


function getRandomSubset($entries, $number)
{
	// Process entries here...
}

PHP provides a ton of array handling functions that simplify the process of manipulating arrays. One of the available functions is called array_rand(), which returns a random assortment of array keys (as a variable if only one element is returned, or as an array if two or more are returned).

We're able to pass two arguments to array_rand(): the first is required and contains the array from which we need random elements, and the second is an optional specifier that allows us to decide how many elements need to be returned (if not set, this defaults to 1).

Because array_rand() only returns the array element keys, we'll need to do a little more coding to get the actual element values. To do this, we'll create an empty array called $randList that will store our random array elements, then run a loop with the random keys to add each element to the array using another array function called array_push(), which adds a value to the end of an array.


function getRandomSubset($entries, $n)
{
	// Initiate the variable
	$randList = array();

	// Pull the desired number of entry keys at random
	$keys = array_rand($entries, $n);

	// Loop through the keys
	foreach($keys as $key) {

		// Add the value to end of the return array
		array_push($randList, $entries[$key]);

	}

	return $randList;
}

To test this code, add the following snippet to a test page. NOTE: The <pre> tags are used to enhance the readability of output generated by print_r().


echo '<pre>';
print_r(getRandomSubset($entries, 2));
echo '</pre>';

Running the code will output something similar to the following:


Array
(
    [0] => Array
        (
            [title] => Entry Four
            [author] => Jason Lengstorf
            [date] => April 11, 2009
        )

    [1] => Array
        (
            [title] => Entry Six
            [author] => Jason Lengstorf
            [date] => April 13, 2009
        )

)

The Problem with Too Few Entries

One problem that exists in our code is that a warning will be issued and no values returned if more entries are requested than available in our entry array. If we call echo getRandomSubset($entries, 9);, an error similar to the following will be output:

Warning: array_rand() [function.array-rand]: Second argument has to be between 1 and the number of elements in the array in /Applications/xampp/xamppfiles/htdocs/test.php on line 75

Warning: Invalid argument supplied for foreach() in /Applications/xampp/xamppfiles/htdocs/test.php on line 78
Array ( )

Solving the Problem

To correct this issue, we need to add a check that ensures the number of entries requested does not exceed the number supplied. If too many entries are requested, we then change the number of requested entries to match the number of available entries, thus avoiding the error and outputting as many entries as possible.


function getRandomSubset($entries, $n)
{
	/*
	 * If $n isn't greater than the number of entries,
	 * reset it to be the number of entries available.
	 */
	$n = (count($entries)<$n) ? count($entries) : $n;

	// Initiate the variable
	$randList = array();

	// Pull the desired number of entry keys at random
	$keys = array_rand($entries, $n);

	// Loop through the keys
	foreach($keys as $key) {

		// Add the value to end of the return array
		array_push($randList, $entries[$key]);

	}

	return $randList;
}

Testing the function should now always result in a subset of the desired length with no duplicate entries. The subset can then be passed to the formatting function of your choice to handle formatting.

Summary

Getting comfortable with the intricacies of loops and arrays can have a huge impact on the cleanliness and readability of your code. What tricks do you have up your sleeve? Let me know in the comments!

Other Notes

For anyone who hasn't seen it yet, I've decided to try and monetize my blog with the help of BuySellAds.com. There are four banner ad spaces available, and they're cheap! Click on the "Advertise Here" boxes in the upper-left of this site to get your ad up today!

I had an article run on NETTUTS called Add Power to Your PHP With Multi-Tiered Applications. It covers what I consider to be best practices while writing code, so I'd love to hear your thoughts on it.

I'm still looking for guest bloggers on Ennui Design, so if you've got an idea, email me and we'll talk about it.

Also, just as a general note, thanks for reading! I really appreciate your comments and support, and I hope to keep supplying you with all the PHP geekery you can handle!

Posted Apr 21, 2009 by Jason Lengstorf.
This entry is filed under instant tip tuesday, php, and arrays.

Want more content like this? Subscribe for FREE!

Comments for This Entry

GravatarFrank08:40AM on April 23, 2009

Why didn't you use mt_rand()? I would use it if I had to write something like this.

GravatarJason Lengstorf10:13AM on April 23, 2009

@Frank:

In this case, because it doesn't really matter how random the entries are, I opted for the built-in function to pull random array keys.

I had originally written another way to do this that used mt_rand(), but it was more complicated.

However, I'd love to see your take on the function. Feel free to post your version in the comments!

GravatarFrank11:13AM on April 23, 2009

@Jason Lengstorf:

Oh, I overlooked the array_rand() function. I think that's a better solution than mt_rand(). :)

But I would do something like this: http://pastebin.com/f271df581

GravatarJason Lengstorf11:29AM on April 23, 2009

@Frank:
That's almost exactly what my original solution was, except I skipped the while(true) by leaving the third expression in the for loop empty:

function get_random_subset($array, $n)
{
$random_items = $random_keys = array();
$item_count = count($array);
$n = $n > $item_count ? $item_count : $n;

for ($x=0; $x < $n; ) {
$r = mt_rand(0, $item_count-1);

if (!in_array($random_items, $array[$r])) {
$random_items[] = $array[$r];
++$x;
}
}

return $random_items;
}


Thanks for your input!

GravatarRudolf Leermakers12:22PM on April 23, 2009

Also came across this problem once, but I wanted to preserve the keys of the original array, so I made this oneliner:

function array_rsubset($array, $count) {
return array_intersect_keys($array, array_flip(array_rand($array, $count)));
}

GravatarJason Lengstorf01:25PM on April 23, 2009

@Rudolf Leermakers:
That function is brilliant! However, the entries are always in ascending order, so you'd have to write another function to shuffle them.

Also, array_intersect_keys() isn't actually a function; I think you were looking for array_intersect_key().

Thanks!

GravatarRudolf Leermakers01:47PM on April 23, 2009

@Jason

Yeah, the intersect_keys() was a typo, I didn't actually copy-paste it, didn't take the time to find where I stored it :)

And for the order-problem, that has to do with the order of the original array, you could shuffle that, or shuffle the end-result in the same line, but I totally missed that problem before, thanks for mentioning it!

Post a Comment

Want to show your face? Get a gravatar!

ALLOWED TAGS: <tt><strong><em>