Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
// This file is part of Moodle - http://moodle.org/
3
//
4
// Moodle is free software: you can redistribute it and/or modify
5
// it under the terms of the GNU General Public License as published by
6
// the Free Software Foundation, either version 3 of the License, or
7
// (at your option) any later version.
8
//
9
// Moodle is distributed in the hope that it will be useful,
10
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
// GNU General Public License for more details.
13
//
14
// You should have received a copy of the GNU General Public License
15
// along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16
 
17
/**
18
 * A {@link \question_variant_selection_strategy} that randomly selects variants that were not used yet.
19
 *
20
 * @package   core_question
21
 * @copyright 2015 The Open University
22
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
23
 */
24
 
25
 
26
namespace core_question\engine\variants;
27
defined('MOODLE_INTERNAL') || die();
28
 
29
 
30
/**
31
 * A {@link \question_variant_selection_strategy} that randomly selects variants that were not used yet.
32
 *
33
 * If all variants have been used at least once in the set of usages under
34
 * consideration, then then it picks one of the least often used.
35
 *
36
 * Within one particular use of this class, each seed will always select the
37
 * same variant. This is so that shared datasets work in calculated questions,
38
 * and similar features in question types like varnumeric and STACK.
39
 *
40
 * @copyright 2015 The Open University
41
 * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42
 */
43
class least_used_strategy implements \question_variant_selection_strategy {
44
 
45
    /** @var array seed => variant number => number of uses. */
46
    protected $variantsusecounts = array();
47
 
48
    /** @var array seed => variant number. */
49
    protected $selectedvariant = array();
50
 
51
    /**
52
     * Constructor.
53
     * @param \question_usage_by_activity $quba the question usage we will be picking variants for.
54
     * @param \qubaid_condition $qubaids ids of the usages to consider when counting previous uses of each variant.
55
     */
56
    public function __construct(\question_usage_by_activity $quba, \qubaid_condition $qubaids) {
57
        $questionidtoseed = array();
58
        foreach ($quba->get_attempt_iterator() as $qa) {
59
            $question = $qa->get_question();
60
            if ($question->get_num_variants() > 1) {
61
                $questionidtoseed[$question->id] = $question->get_variants_selection_seed();
62
            }
63
        }
64
 
65
        if (empty($questionidtoseed)) {
66
            return;
67
        }
68
 
69
        $this->variantsusecounts = array_fill_keys($questionidtoseed, array());
70
 
71
        $variantsused = \question_engine::load_used_variants(array_keys($questionidtoseed), $qubaids);
72
        foreach ($variantsused as $questionid => $usagecounts) {
73
            $seed = $questionidtoseed[$questionid];
74
            foreach ($usagecounts as $variant => $count) {
75
                if (isset($this->variantsusecounts[$seed][$variant])) {
76
                    $this->variantsusecounts[$seed][$variant] += $count;
77
                } else {
78
                    $this->variantsusecounts[$seed][$variant] = $count;
79
                }
80
            }
81
        }
82
    }
83
 
84
    public function choose_variant($maxvariants, $seed) {
85
        if ($maxvariants == 1) {
86
            return 1;
87
        }
88
 
89
        if (isset($this->selectedvariant[$seed])) {
90
            return $this->selectedvariant[$seed];
91
        }
92
 
93
        // Catch a possible programming error, and make the problem clear.
94
        if (!isset($this->variantsusecounts[$seed])) {
95
            debugging('Variant requested for unknown seed ' . $seed . '. ' .
96
                    'You must add all questions to the usage before creating the least_used_strategy. ' .
97
                    'Continuing, but the variant choses may not actually be least used.',
98
                    DEBUG_DEVELOPER);
99
            $this->variantsusecounts[$seed] = array();
100
        }
101
 
102
        if ($maxvariants > 2 * count($this->variantsusecounts[$seed])) {
103
            // Many many more variants exist than have been used so far.
104
            // It will be quicker to just pick until we miss a collision.
105
            do {
106
                $variant = rand(1, $maxvariants);
107
            } while (isset($this->variantsusecounts[$seed][$variant]));
108
 
109
        } else {
110
            // We need to work harder to find a least-used one.
111
            $leastusedvariants = array();
112
            for ($variant = 1; $variant <= $maxvariants; ++$variant) {
113
                if (!isset($this->variantsusecounts[$seed][$variant])) {
114
                    $leastusedvariants[$variant] = 1;
115
                }
116
            }
117
            if (empty($leastusedvariants)) {
118
                // All variants used at least once, try again.
119
                $leastuses = min($this->variantsusecounts[$seed]);
120
                foreach ($this->variantsusecounts[$seed] as $variant => $uses) {
121
                    if ($uses == $leastuses) {
122
                        $leastusedvariants[$variant] = 1;
123
                    }
124
                }
125
            }
126
            $variant = array_rand($leastusedvariants);
127
        }
128
 
129
        $this->selectedvariant[$seed] = $variant;
130
        if (isset($variantsusecounts[$seed][$variant])) {
131
            $variantsusecounts[$seed][$variant] += 1;
132
        } else {
133
            $variantsusecounts[$seed][$variant] = 1;
134
        }
135
        return $variant;
136
    }
137
}