Page 1 of 1

Generating questions from CSV file

Posted: Sun Apr 25, 2010 3:14 am
by drimades
Hi!
I have a CSV file with some sample questions. The fields of the CSV file are as follows:

number of the question, text of the question, points of the question

Now I m reading the file in php and trying to generate a number of questions from it.

Code: Select all

<?PHP

$file_handle = fopen("hum131cepele.csv", "r");

$tezat = array();
//print "<table border=1>";
$i = 0;
$count_lines = 0;
while (!feof($file_handle) ) {

$line_of_text = fgetcsv($file_handle, 1024);

$count_lines = $count_lines + 1;

};

echo "<br>";
// print "</table>";
$rndnrs = array();
while ( count($rndnrs) < 10 ) { // generate 10 unique random numbers
    $x = mt_rand(1,$count_lines);
    if ( !in_array($x,$rndnrs) ) { $rndnrs[] = $x; }
}
rewind($file_handle);

// printing the questions of the test
print "<table border=1>";
$totpoints = 0; // total point for the test
$currpoints = 0;
while (!feof($file_handle) && $totpoints < 20) {

$line_of_text = fgetcsv($file_handle, 1024);
$current = (int)$line_of_text[0];
if (in_array($current,$rndnrs))
    {
         print "<tr>";
         print "<td>".$line_of_text[0] ."</td><td>". $line_of_text[1]."</td><td>". $line_of_text[2]."</td>";
         $currpoints = (int)$line_of_text[2];
         $totpoints = $totpoints + $currpoints;
         print "</tr>";
    };
};
fclose($file_handle);

?>
I need to generate a test with 20 points total. The questions have different amount of points (2, 3, 4, etc.) and of course with the solution above it generates a test with more than 20 points. Any idea?

Re: Generating questions from CSV file

Posted: Sun Apr 25, 2010 5:33 am
by requinix
Roughly how many questions are there to choose from? (I'm thinking about efficiency.)

Re: Generating questions from CSV file

Posted: Sun Apr 25, 2010 5:43 am
by drimades
tasairis wrote:Roughly how many questions are there to choose from? (I'm thinking about efficiency.)
in average 50. however, not more than 100.

Re: Generating questions from CSV file

Posted: Sun Apr 25, 2010 5:09 pm
by requinix
The subset sum problem
If you want an exact answer it's going to hurt.

Any reason it must have 20 points? I suppose you can't change that to be (eg) 10 random problems and however many points they add up to?

Re: Generating questions from CSV file

Posted: Sun Apr 25, 2010 6:29 pm
by phu
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";
}

Re: Generating questions from CSV file

Posted: Mon Apr 26, 2010 4:27 am
by Benjamin
Here's another version:

Code: Select all

<?php
class createTest {
    private
      $_totalPoints     = 0,
      $_questionSet     = array(),
      $_attempts        = 0,
      $_maxAttempts     = 100;

    public function __construct() {
        $this->_reset();
    }

    public function createQuestions($maxQuestions = 100, $minScore = 1, $maxScore = 5) {
        $this->minScore = $minScore;
        $this->maxScore = $maxScore;

        foreach (range(0,$maxQuestions) as $k => $v) {
            $this->_questionSet[$k] = array(
                'id'            => $k,
                'question'      => "How do you feel about the number $v?",
                'points'        => mt_rand($this->minScore,$this->maxScore),
            );
        }

        shuffle($this->_questionSet);
        return $this;
    }

    private function _countTry() {
        if ($this->_maxAttempts <= ++$this->_attempts) {
            throw new Exception('Too many retries');
        }
    }

    private function _reset() {
        $this->_points          = 0;
        $this->_usedQuestions   = array();
        $this->_finalTest       = array();
        $this->_countTry();
    }

    public function createTest($totalPoints = 20) {
        $this->_totalPoints = $totalPoints;
        $this->_reset();

        for ($this->_points = 0; $this->_points <= $this->_totalPoints; $this->_points += 0) {
            $q = array_rand($this->_questionSet);

            if (isset($this->_usedQuestions[$this->_questionSet[$q]['id']])) {
                continue;
            }

            $this->addQuestion($this->_questionSet[$q]['id'], $this->_questionSet[$q]['question'], $this->_questionSet[$q]['points']);
        }

        return $this;
    }

    public function addQuestion($id, $question, $points) {
        $this->_usedQuestions[$id] = true;
        $this->_finalTest[$points][$id] = array('id' => $id, 'question' => $question, 'points' => $points);
        $this->_points += $points;
    }

    public function removeQuestion($id) {
        $this->_points -= $this->_questionSet[$id]['points'];
        unset($this->_usedQuestions[$id]);
    }

    public function generateQuestions() {
        while ($this->_points != $this->_totalPoints) {
            $diff = ($this->_points + $this->minScore) - $this->_totalPoints;

            if (isset($this->_finalTest[$diff]) && is_array($this->_finalTest[$diff]) && null !== $k = array_pop($this->_finalTest[$diff])) {
                $this->removeQuestion($k['id']);
                foreach ($this->_questionSet as $question) {
                    if ($question['points'] != $this->minScore || isset($this->_usedQuestions[$question['id']])) {
                        continue;
                    }

                    $this->addQuestion($question['id'], $question['question'], $question['points']);
                    break(2);
                }
            }

            $this->createTest($this->_totalPoints);
        }

        return $this;
    }

    public function getQuestions() {
        $questions = array();

        foreach ($this->_finalTest as $questionSet) {
            foreach ($questionSet as $question) {
                $questions[] = $question;
            }
        }

        shuffle($questions);

        return $questions;
    }
}
Usage:

Code: Select all

echo "<pre>";

$createTest = new createTest();

$questions = $createTest
    ->createQuestions()
    ->createTest(20)
    ->generateQuestions()
    ->getQuestions();

$points = 0;

foreach ($questions as $question) {
    $points += $question['points'];
}

echo count($questions) . " questions for $points points\n\n";

print_r($questions);
Cheers :drunk: