View Javadoc

1   /**
2    * 
3    */
4   package sk.stuba.fiit.foo07.genex.generator;
5   
6   import java.sql.Connection;
7   import java.sql.SQLException;
8   import java.util.ArrayList;
9   import java.util.Collections;
10  import java.util.Random;
11  
12  import sk.stuba.fiit.foo07.genex.beans.Question;
13  import sk.stuba.fiit.foo07.genex.beans.QuestionPoints;
14  import sk.stuba.fiit.foo07.genex.beans.Test;
15  import sk.stuba.fiit.foo07.genex.dao.QuestionDao;
16  import sk.stuba.fiit.foo07.genex.dao.QuestionDaoDerby;
17  import sk.stuba.fiit.foo07.genex.exceptions.NotEnoughQuestionsException;
18  
19  /**
20   * @author Martin Michalek
21   * 
22   * This class represents a test generator which selects questions based on the
23   * desired difficulty of generated test.
24   */
25  public class TestGenerator {
26  
27      //private helpers
28      private QuestionDao questionDao;
29      private ArrayList<Question> possibleQuestions;
30      private ArrayList<ArrayList<Question>> possibleQuestionsByDifficulty;
31      private ArrayList<Question> selectedQuestions;
32      private float difficultyAchieved;
33      private Random rand;
34  
35      //generating parameters
36      private ArrayList<Integer> questionCategoryIDs;
37      private ArrayList<Integer> keywordIDs;
38      private ArrayList<Integer> questionTypeIDs;
39      private int difficultyDesired;
40      private int questionCount;
41      private int pointsSummDesired;
42      private boolean containsPictures;
43      private ArrayList<Integer> difficultiesCount;
44  
45      //result values
46      private Test generatedTest;
47  
48      /**
49       * @return the generatedTest
50       */
51      public Test getGeneratedTest() {
52          return generatedTest;
53      }
54  
55      /**
56       * 
57       * @param con
58       *                database connection
59       * @param questionCategoryIDs
60       *                list of category IDs which specify the initial set of
61       *                questions for test generating
62       * @param keywordIDs
63       *                list of keyword IDs which specify the initial set of
64       *                questions for test generating
65       * @param questionTypeIDs
66       *                types of questions for the test
67       * @param difficultyDesired
68       *                test difficulty that we want to achieve
69       * @param questionCount
70       *                number of questions in the test
71       * @param pointsSummDesired
72       *                the point value of the test we want to achieve
73       * @param containsPictures
74       *                false if the test must not contain questions with pictures
75       */
76      public TestGenerator(Connection con,
77              ArrayList<Integer> questionCategoryIDs,
78              ArrayList<Integer> keywordIDs, ArrayList<Integer> questionTypeIDs,
79              int difficultyDesired, int questionCount, int pointsSummDesired,
80              boolean containsPictures) {
81          super();
82          this.rand = new Random();
83          this.questionCategoryIDs = questionCategoryIDs;
84          this.keywordIDs = keywordIDs;
85          this.questionTypeIDs = questionTypeIDs;
86          this.difficultyDesired = difficultyDesired;
87          this.questionCount = questionCount;
88          this.pointsSummDesired = pointsSummDesired;
89          this.containsPictures = containsPictures;
90          this.questionDao = new QuestionDaoDerby(con);
91      }
92  
93      public TestGenerator(Connection con,
94              ArrayList<Integer> questionCategoryIDs,
95              ArrayList<Integer> keywordIDs, ArrayList<Integer> questionTypeIDs,
96              int questionCount, int pointsSummDesired, boolean containsPictures,
97              ArrayList<Integer> difficultiesCount) {
98          super();
99          this.rand = new Random();
100         this.questionCategoryIDs = questionCategoryIDs;
101         this.keywordIDs = keywordIDs;
102         this.questionTypeIDs = questionTypeIDs;
103         this.questionCount = questionCount;
104         this.pointsSummDesired = pointsSummDesired;
105         this.containsPictures = containsPictures;
106         this.questionDao = new QuestionDaoDerby(con);
107         this.difficultiesCount = difficultiesCount;
108     }
109 
110     private float countProbability(float mean, float stdDeviation, float x) {
111         if (difficultyDesired == 0) {
112             return 1.0f;
113         } else {
114             return (float) ((1 / (Math.sqrt(2 * Math.PI) * stdDeviation)) * Math
115                     .exp(-(1 / (2 * stdDeviation * stdDeviation))
116                             * Math.pow(x - mean, 2)));
117         }
118     }
119 
120     private float getStdDeviation() {
121         if ((difficultyDesired == 1) || (difficultyDesired == 5)) {
122             return 0.8f;
123         } else if ((difficultyDesired == 2) || (difficultyDesired == 4)) {
124             return 1.0f;
125         } else
126             return 1.25f;
127     }
128 
129     private int getDeterministicSelection() {
130         int oneFifth = (possibleQuestions.size() / 5);
131         float reminder = (possibleQuestions.size() % 5) / 2;
132         if (difficultyDesired < 3) {
133             return (oneFifth * (difficultyDesired - 1)) + myRandom(oneFifth);
134         } else if (difficultyDesired == 3) {
135             return (oneFifth * (difficultyDesired - 1) + (int) (reminder) + myRandom(oneFifth
136                     + Math.round(reminder))) - 1;
137         } else
138             return possibleQuestions.size() - 1
139                     - ((6 - difficultyDesired) * oneFifth) + myRandom(oneFifth);
140     }
141 
142     private int myRandom(int i) {
143         if (i == 0)
144             return 0;
145         else
146             return rand.nextInt(i);
147     }
148 
149     private void selectQuestions() {
150 
151         boolean wasSelected = false;
152         float actualTestDifficulty = 3.0f;
153         float stdDeviation = 0.0f;
154         float randomQuestionProbability;
155         int randomSellection = 0;
156         int deterministicSelection = 0;
157         Question toAdd = null;
158 
159         int hit = 0;
160 
161         selectedQuestions = new ArrayList<Question>(questionCount);
162 
163         for (int i = 0; i < questionCount; i++) {
164             stdDeviation = getStdDeviation();
165             wasSelected = false;
166 
167             for (int j = 0; j < possibleQuestions.size(); j++) {
168                 randomSellection = rand.nextInt(possibleQuestions.size());
169                 toAdd = possibleQuestions.get(randomSellection);
170                 if (Math.abs(toAdd.getDifficulty() - difficultyDesired) > 2) {
171                     continue;
172                 }
173                 randomQuestionProbability = countProbability(difficultyDesired,
174                         stdDeviation, toAdd.getDifficulty());
175 
176                 if (rand.nextFloat() < randomQuestionProbability) {
177                     selectedQuestions.add(i, toAdd);
178                     possibleQuestions.remove(randomSellection);
179                     wasSelected = true;
180                     System.out.println("Selested question: "
181                             + toAdd.getDifficulty() + " " + difficultyDesired);
182                     break;
183                 }
184             }
185 
186             if (!wasSelected) {
187                 deterministicSelection = getDeterministicSelection();
188                 if (deterministicSelection >= possibleQuestions.size()
189                         || deterministicSelection == -1) {
190                     deterministicSelection = possibleQuestions.size() - 1;
191                 }
192                 if (possibleQuestions.size() == 0) {
193                     return;
194                 }
195                 toAdd = possibleQuestions.get(deterministicSelection);
196                 selectedQuestions.add(i, toAdd);
197                 possibleQuestions.remove(deterministicSelection);
198                 hit++;
199             }
200             actualTestDifficulty = (actualTestDifficulty * i + toAdd
201                     .getDifficulty())
202                     / (i + 1);
203         }
204         difficultyAchieved = actualTestDifficulty;
205         System.out.println("hits: " + hit);
206     }
207 
208     private float round(float f) {
209         return Math.round(f * 2) / 2f;
210     }
211 
212     private void countPoints() {
213         ArrayList<QuestionPoints> points = new ArrayList<QuestionPoints>(
214                 questionCount);
215         float point = 0.0f;
216 
217         if (pointsSummDesired == 0) {
218             for (Question q : selectedQuestions) {
219                 point = q.getDifficulty();
220                 points.add(new QuestionPoints(q.getQuestionID(), point));
221             }
222         } else {
223             float pointsDifficultyKoeficient = pointsSummDesired
224                     / (difficultyAchieved * questionCount);
225 
226             for (Question q : selectedQuestions) {
227                 point = this.round(pointsDifficultyKoeficient
228                         * q.getDifficulty());
229                 points.add(new QuestionPoints(q.getQuestionID(), point));
230             }
231         }
232 
233         generatedTest = new Test(null, null, null, null, null, null, points);
234 
235     }
236 
237     /**
238      * Generates test based on input parameters. Results are stored in class
239      * fields and can be obtained using getter methods: getGeneratedTest(),
240      * getDifficultyAchieved(), getPointsSummAchieved()
241      * 
242      * @throws NotEnoughQuestionsException
243      * @throws SQLException
244      */
245     public void generateTest() throws NotEnoughQuestionsException, SQLException {
246         possibleQuestions = questionDao.getQuestionsByIDs(questionDao
247                 .getQuestionsForTestGenerator(questionCategoryIDs, keywordIDs,
248                         questionTypeIDs, containsPictures, null));
249         if (possibleQuestions.size() < questionCount) {
250             throw new NotEnoughQuestionsException();
251         }
252         Collections.shuffle(possibleQuestions);
253         Collections.sort(possibleQuestions);
254         //System.out.println("Initial size: " + possibleQuestions.size());
255         selectQuestions();
256         System.out.println("Size after: " + possibleQuestions.size());
257         countPoints();
258         //TODO vyriesit zavyslosti otazok
259     }
260 
261     public void assembleTest() throws NotEnoughQuestionsException, SQLException {
262         int possibleQuestionCount = 0;
263         possibleQuestionsByDifficulty = new ArrayList<ArrayList<Question>>(5);
264         for (int i = 0; i < 5; i++) {
265             possibleQuestions = questionDao.getQuestionsByIDs(questionDao
266                     .getQuestionsForTestGenerator(questionCategoryIDs,
267                             keywordIDs, questionTypeIDs, containsPictures,
268                             i + 1));
269             possibleQuestionsByDifficulty.add(i, possibleQuestions);
270 
271             if (possibleQuestions.size() < difficultiesCount.get(i)) {
272                 throw new NotEnoughQuestionsException(
273                         "Not enough questions of difficulty " + (i + 1)
274                                 + " to generate Test.");
275             }
276             possibleQuestionCount += possibleQuestions.size();
277         }
278 
279         if (possibleQuestionCount < questionCount) {
280             throw new NotEnoughQuestionsException(
281                     "Not enough questions to generate Test.");
282         }
283 
284         Question toAdd = null;
285         selectedQuestions = new ArrayList<Question>(questionCount);
286         int randomIndex;
287         int difficultyCount;
288         int numberOfQuestionsSelected = 0;
289         difficultyAchieved = 0;
290 
291         for (int j = 0; j < 5; j++) {
292             difficultyCount = difficultiesCount.get(j);
293             possibleQuestions = possibleQuestionsByDifficulty.get(j);
294             for (int i = 0; i < difficultyCount; i++) {
295                 randomIndex = rand.nextInt(possibleQuestions.size());
296                 toAdd = possibleQuestions.get(randomIndex);
297                 selectedQuestions.add(toAdd);
298                 possibleQuestions.remove(randomIndex);
299                 difficultyAchieved = difficultyAchieved + toAdd.getDifficulty();
300                 numberOfQuestionsSelected++;
301             }
302         }
303         for (int i = numberOfQuestionsSelected; i < questionCount; i++) {
304             do {
305                 randomIndex = rand.nextInt(5);
306                 possibleQuestions = possibleQuestionsByDifficulty
307                         .get(randomIndex);
308             } while (possibleQuestions.size() <= 0);
309             randomIndex = rand.nextInt(possibleQuestions.size());
310             toAdd = possibleQuestions.get(randomIndex);
311             selectedQuestions.add(toAdd);
312             possibleQuestions.remove(randomIndex);
313             difficultyAchieved = difficultyAchieved + toAdd.getDifficulty();
314 
315         }
316         difficultyAchieved = difficultyAchieved / questionCount;
317         countPoints();
318     }
319 }