在线考试系统提供了根据教师制定的出题策略随机生成试卷的功能。下面看看出题策略部分的详细类图4-15
图4-15 出题策略模块类图
可以看出该部分和试卷部分的类图有些相似。这里PaperStrategy和Paper类对应。PaperStrategy(出题策略)是用来生成Paper的。StrategyContainer是用来生成QuestionContainer的。而StrategyItem(策略项)是用来生成试题Question的。下面先从整体上介绍出题策略的概念。出题策略就是一个生成试卷的模板。这个模板制定了要出几个大题。每个大题包含了那些小题。出几个大题比较简单想要出几个大题就给一个出题策略PaperStrategy对象创建几个StrategyContainer子对象就可以了。关键是每个大题包含那些小题比较复杂。这里引入策略项的概念(StrategyItem)。每个大题可以有多个策略项。每个策略项有个QuestionContent集合和分值(ScoreValue),出题数(Count)属性。策略项的GetQuesitons方法会从引用的QuestionContent对象中随机挑出Count个,并根据ScoreValue属性生成Count个分值为ScoreValue的Question对象。每个策略项引用的多个QuestionContent对象代表了老师想出的一个知识点中的备选题。下面代码是PaperStrategy类的 GetPaper
方法。该方法创建一个新的Paper对象并遍历每个StrategyContainer子对象。然后将每个StrategyContainer生成的大题QuestionContainer对象加入到新建的Paper对象中。
{
Paper paper = new Paper();
paper.IsSubmited = false;
foreach (StrategyContainer container in strategyContainers)
{
QuestionContainer qustionContainer = container.GetQuestionContainer();
qustionContainer.Paper = paper;
paper.QuestionContainers.Add(qustionContainer);
}
return paper;
}
下面再看看每个StrategyContainer是如何生成QuestionContainer对象的。下面是StrategyContainer类的GetQuestionContainer方法
{
QuestionContainer result = new QuestionContainer();
result.Title = title;
foreach (StrategyItem item in strategyItems)
{
IList<Question> questions = item.GetQuestions();
foreach (Question q in questions)
{
q.QuestionContainer = result;
result.Questions.Add(q);
}
}
return result;
}
上面方法创建并返回了一个新的QuestionContainer对象,并遍历自己的StrategyItem子对象。将每个StrategyItem生成的试题Question对象全部加入到新建的QuestionContainer对象。
最后再来看下StrategyItem是如何随机地从指定的出题范围返回指定数量的Question对象的。下面是StrategyItem的GetQuestions方法。
{
IList<Question> result = new List<Question>();
IList<int> sourceList = new List<int>();
for (int i = 0; i < QuestionContents.Count; i++)
sourceList.Add(i);
IList<int> resultList = RandomNumberHelper.RandomSelect(sourceList, count);
foreach (int i in resultList)
{
Question question = new Question();
question.Content = QuestionContents[i];
question.ScoreValue = ScoreValue;
result.Add(question);
}
return result;
}
该方法根据RandomNumberHelper.RandomSelect来随机的从StrategyItem的QuestionContents集合属性中随机的选择指定数目的QuestionContents集合的一个子集。并根据子集来生成一个Question对象的集合并返回。这样就完成了根据出题策略生成试卷的功能。下面介绍下随机出题的算法。即从QuestionContents集合中随机选取指定数目的QuestionContent对象。该集合的定义如下。
IList<QuestionContent> QuestionContents = new List<QuestionContent>();
因为是用List来存储的。所以抽取题目的方法是。随机的选取QuestionContents集合的几个下标。比如QuestionContents有5个题目想选两个, 可以从0~4五个数字中选择两个不同的数字.比如选择了1、3。然后就可以通过QuestionContents[1],QuestionContents[3]取出要的两个题目。下面给出如何从一个下标的范围中随机选取指定数目下标的算法。
{
public static IList<int> RandomSelect(IList<int> sourceList, int selectCount)
{
if (selectCount > sourceList.Count)
throw new ArgumentOutOfRangeException("selectCount必需大于sourceList.Count");
IList<int> resultList = new List<int>();
for (int i = 0; i < selectCount; i++)
{
int nextIndex = GetRandomNumber(1, sourceList.Count);
int nextNumber = sourceList[nextIndex - 1];
sourceList.RemoveAt(nextIndex - 1);
resultList.Add(nextNumber);
}
return resultList;
}
public static int GetRandomNumber(int minValue, int maxValue)
{
return random.Next(minValue, maxValue + 1);
}
private static Random random = new Random();
}
RandomSelect方法从给定下标集合sourceList中随机的选取selectCount个并将选取结果放到resultList中返回。该算法比网上用递归的方式实现算法效率要高。因为该算法在从sourceList中选出一个下标后就将该下标从sourceList中删除。所以下次在从sourceList中选的时候就不可能再重复挑出已经挑选过的下标。这样就避免了因为随机数不确定性带来的重复筛选的问题。