This will generate a test with questions whose points total 20.
It's not an ideal solution (there pretty much isn't one, as tasairis pointed out; the problem is NP-complete). It has the possibility of hitting a brick wall -- that is, generating a test composed of questions that can't end up totalling 20. It does detect this and try again.
I wasn't going to go through the whole thing, but I got a little obsessive... this isn't perfect, but it seems to be building tests composed of unique questions with points adding to 20 (configurable), and retrying when it fails.
Obviously it'd take some tweaking to actually use it (and I'm pretty sure I went waaaaay overboard with the applications of the reference operator; it got a bit frustrating and I didn't really feel like picking it apart that much), but it seems to be doing its job.
Code: Select all
<?php
function by_points(&$questions, &$values)
{
$values = array();
foreach ($questions as $key => &$question)
{
$points = $question['points'];
if (!$values[$points])
{
$values[$points] = array();
}
$values[$points][] = &$questions[$key];
}
}
$questions = array(
array('question' => 'Why?1', 'points' => 2),
array('question' => 'Why?2', 'points' => 4),
array('question' => 'Why?3', 'points' => 5),
array('question' => 'Why?4', 'points' => 2),
array('question' => 'Why?5', 'points' => 2),
array('question' => 'Why?6', 'points' => 4),
array('question' => 'Why?7', 'points' => 3),
array('question' => 'Why?8', 'points' => 3),
array('question' => 'Why?9', 'points' => 2),
);
$question_cache = array_merge($questions);
$values = array();
by_points($questions, $values);
$point_values = array_keys($values);
$total_points = 20;
$current_points = 0;
$min_points = min($point_values);
$max_points = max($point_values);
$test = array();
srand(time());
while ($current_points != $total_points)
{
$difference = $total_points - $current_points;
$question = false;
if ($difference < $min_points)
{
echo "Failed to create 20-point test; resetting\n";
$current_points = 0;
$test = array();
$questions = array_merge($question_cache);
by_points($questions, $values);
$point_values = array_keys($values);
$min_points = min($point_values);
$max_points = max($point_values);
continue;
}
else if ( ($difference <= $max_points) && in_array($difference, $point_values) )
{
$index = rand(0, count($values[$difference]) - 1);
$question = $values[$difference][$index];
}
else
{
$index = rand(0, count($questions) - 1);
$question = $questions[$index];
unset($questions[$index]);
$offset = array_keys($values[$question['points']], $question);
unset($values[$question['points']][$offset[0]]);
}
if ($question)
{
$test[] = $question;
$current_points += $question['points'];
$questions = array_values($questions);
if (in_array($difference, $point_values))
{
if (!count($values[$difference]))
{
unset($values[$difference]);
unset($point_values[$difference]);
}
else
{
$values[$difference] = array_values($values[$difference]);
}
}
$min_points = min($point_values);
$max_points = max($point_values);
}
else
{
echo "Couldn't get a question for {$difference}.\n";
$min_points = 20;
}
}
foreach ($test as $question)
{
echo "{$question['question']} ({$question['points']} pt)\n";
}