Generating questions from CSV file

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Post Reply
drimades
Forum Newbie
Posts: 2
Joined: Sat Apr 24, 2010 11:13 am

Generating questions from CSV file

Post 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?
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: Generating questions from CSV file

Post by requinix »

Roughly how many questions are there to choose from? (I'm thinking about efficiency.)
drimades
Forum Newbie
Posts: 2
Joined: Sat Apr 24, 2010 11:13 am

Re: Generating questions from CSV file

Post 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.
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: Generating questions from CSV file

Post 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?
phu
Forum Commoner
Posts: 61
Joined: Tue Mar 30, 2010 6:18 pm

Re: Generating questions from CSV file

Post 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";
}
User avatar
Benjamin
Site Administrator
Posts: 6935
Joined: Sun May 19, 2002 10:24 pm

Re: Generating questions from CSV file

Post 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:
Post Reply