P
- is for answer Part. A subclass of AnswerValuePart
representing a single part
of a teacher's answer value or a student's response value.
A
- is for Answer. A subclass of Answer
representing a teacher-provided answer for a concrete
problem type.
Each Answer subclass depends on:
- P — AnswerValuePart subclass
- V — TeacherAnswerValue subclass
- R — StudentResponseValue subclass
V
- is for answer Value. A subclass of TeacherAnswerValue
representing a correct or incorrect teacher-provided
answer value.
Each TeacherAnswerValue subclass depends on:
- P — AnswerValuePart subclass
- R — StudentResponseValue subclass
R
- is for student Response. A subclass of StudentResponseValue
representing a
student's response to a problem.
Each TeacherAnswerValue subclass depends on:
- P — AnswerValuePart subclass
public abstract class AnswerableProblem<P extends AnswerValuePart,A extends Answer<P,V,R>,V extends TeacherAnswerValue<P,R>,R extends StudentResponseValue<P>> extends Problem
Each concrete AnswerableProblem subclass implements a distinct problem type. The problem type inherently dictates:
For example, the question "What is the sum of: 1/2 + 1/2 +1/2?" could have the following answers all of which might be set as correct: 1.5; 3/2; 1 1/2.
The following diagram summarizes the base classes needed to subclass
AnswerableProblem and create a concrete problem type:
Briefly:
Answer
s.
TeacherAnswerValue
instance (which extends AnswerValue
).
AnswerValuePart
s.
AnswerValuePart
to store each part of an answer's value.
AnswerEvalType
directing how ASSISTments evaluates the string value.
AnswerEvalType.STRING
, ASSISTments evaluates
the student's response to the question by comparing String values (the student's response and the
answer's value).
For a problem type with multiple value part answers, you are free to assign
each value part a different evaluation type. A fill in problem with units
provides an example: The first part, the value a student calculates to answer the question,
might be evaluated as an AnswerEvalType.NUMERIC
,
while the second part, the value's units, would be evaluated as a STRING value.
The enum AnswerEvalType
lists the supported evaluation types.
StudentResponseValue
(which extends AnswerValue
).
In many cases a response value contains the same number of answer value parts as that problem's TeacherAnswerValue subclass. For example a fill-in problem's response class expects a single value part (to be matched against the fill-in problem's answer list).
However, it is possible that the StudentResponseValue part count differs from the TeacherAnswerValue part count. For instance, the answer value for a range problem consists of two parts, a lower- and an upper-limit value. Range problems are constructed such that ASSISTment's looks to see if the student's response, a single value, falls between the those two limits. In this case, you define the range problem's response value object to contain a single value part.
FillInProblem
as an example, a new concrete problem type requires the
following concrete classes:
registerProblemTypeAttributes()
in a static initializer block. FillInProblem
Answer
.FillInAnswer
TeacherAnswerValue
to represent answer values
provided by content builders.FillInAnswerValue
StudentResponseValue
to represent a student's response to the
problem's question.FillInResponseValue
AnswerValuePart
to represent a single part of an answer's value.
FillInAnswerValuePart
To assist, AnswerableProblem provides a default semantic for checking the correctness of a student's response. The response is declared correct if both these conditions are true:
AnswerableProblem.isCorrectDefault(StudentResponseValue)
and
Answer#isMatchDefault(StudentResponseValue)
TeacherAnswerValue#isMatchDefault(StudentResponseValue)
AnswerValuePart#isMatchDefault(AnswerValuePart)
TeacherAnswerValue
and the
StudentResponseValue
contain the same number and types of value parts, then:
AnswerableProblem.isCorrect(StudentResponseValue)
by calling
AnswerableProblem.isCorrectDefault(StudentResponseValue)
, and
Answer#isMatch(StudentResponseValue)
by calling
Answer#isMatchDefault(StudentResponseValue)
, and
TeacherAnswerValue#isMatch(StudentResponseValue)
by calling
TeacherAnswerValue#isMatchDefault(StudentResponseValue)
, and
AnswerValuePart#isMatch(AnswerValuePart)
by calling
AnswerValuePart#isMatchDefault(AnswerValuePart)
For some subclass problem types, calling isCorrectDefault() is not suitable and those subclasses may need to provide their own full implementations for isCorrect() and/or isMatch().
The SortingProblem
provides an example of a problem type where declaring each answer as
correct or incorrect is meaningless. In SortingProblem, the "answers" represent the
values to be ordered by a student. SortingProblem compares the ordering specified by the
student to the correct ordering of the displayed values (the "answers"). Accordingly,
SortingProblem completely implements that semantic in isCorrect() and has no
need to call isMatch().
To actually implement a new problem type, it is best to proceed starting at the answer value part level and working your way up to the new problem class itself.
Using a new problem type, say the "Uber Problem" type, the following steps outline how to implement an UberProblem class based on AnswerableProblem:
AnswerValuePart
AnswerValuePart#isMatch(AnswerValuePart)
. There are
three possibilities:
AnswerValuePart#isMatchDefault(AnswerValuePart)
provides
the proper semantics, implement isMatch() to call isMatchDefault().
AnswerableProblem.isCorrect(StudentResponseValue)
,
implement the required
AnswerValuePart#isMatch(AnswerValuePart)
method to call
the static method Answer.preventIsMatch(String)
. That reflects that you never expect code to
call isMatch().
StudentResponseValue
. Generally
speaking subclasses of StudentResponseValue are thin wrapper classes. They serve to help
completely define a problem type (having a problem, teacher answer, student response, etc).
TeacherAnswerValue
.
TeacherAnswerValue#isMatch(StudentResponseValue)
.
Again, there are three possibilities:
TeacherAnswerValue#isMatchDefault(StudentResponseValue)
provides
the proper semantics, implement isMatch() to call isMatchDefault().
AnswerableProblem.isCorrect(StudentResponseValue)
,
implement the required
TeacherAnswerValue#isMatch(StudentResponseValue)
method to call
the static method Answer.preventIsMatch(String)
. That reflects that you never expect code to call
isMatch().
Answer
.
Answer#isMatch(StudentResponseValue)
TeacherAnswerValue#isMatch(StudentResponseValue)
and AnswerValuePart#isMatch(AnswerValuePart)
.
AnswerableProblem
.
AnswerableProblem.registerProblemTypeAttributes()
.
addAnswer(String, boolean, AnswerValuePart...)
makeResponse(String...)
SubmittableProblem
Modifier and Type | Field and Description |
---|---|
protected static java.util.List<java.lang.String> |
answerableClasses |
protected java.util.Map<java.lang.String,java.lang.String> |
probProperties |
defaultResponseDescription
Modifier | Constructor and Description |
---|---|
protected |
AnswerableProblem(java.lang.String classType,
java.lang.String uid)
Constructor
|
protected |
AnswerableProblem(java.lang.String classType,
java.lang.String uid,
java.lang.String name,
java.lang.String description) |
Modifier and Type | Method and Description |
---|---|
void |
addAnswer(A answer)
Adds an answer to the list of answers.
|
protected abstract A |
addAnswer(java.lang.String uid,
boolean isCorrect,
P... valueParts)
Given a list of value parts, returns a subclass instance of an
Answer containing
those values. |
void |
addProperty(java.lang.String key,
java.lang.String value) |
A |
getAnswer(int index)
Returns a particular answer.
|
int |
getAnswerCount()
Returns the current number of answers for this problem.
|
AnswerEvalType |
getAnswerEvalType(int i)
Returns the evaluation type for the specified value part in this problem's answers.
|
java.util.List<AnswerEvalType> |
getAnswerEvalTypes()
Returns the list of evaluation types for each value part in this problem's answers.
|
java.util.List<A> |
getAnswers()
Returns the answers for this problem as they were ordered by the problem creator.
|
java.util.List<A> |
getAnswers(boolean enableRandom)
Returns the answers for this problem.
|
java.util.List<P> |
getAnswerValuePartsList()
Returns, in a single list, the answer value parts from all of the answers to this problem.
|
A |
getCorrectAnswer()
Returns the correct Answer, if any, matched to the student's response.
|
AnswerableProblemAttributes |
getProblemTypeAttributes()
Returns the problem type properties for this instance's class type.
|
static AnswerableProblemAttributes |
getProblemTypeAttributes(java.lang.String classType)
Returns a problem type's properties.
|
java.util.Map<java.lang.String,java.lang.String> |
getProperties() |
java.lang.String |
getProperty(java.lang.String key) |
AnswerEvalType |
getResponseEvalType(int i)
Returns the evaluation type for the specified value part in this problem's response.
|
java.util.List<AnswerEvalType> |
getResponseEvalTypes()
Returns the list of evaluation types for each value part in this problem's response.
|
java.lang.String |
getUnscrambledAnswerIndexes(java.lang.String responseIndexes)
Returns a space-separated list of numbers where each number represents an index
into this problem's original, non-randomized, answer list.
|
protected java.lang.String |
getUnscrambledAnswerIndexes(java.lang.String responseIndexes,
boolean sort)
See
getUnscrambledAnswerIndexes(String) for the method description. |
A |
getWrongAnswer()
Returns the wrong Answer, if any, matched to the student's response.
|
boolean |
isAllowedAnswerEvalType(AnswerEvalType evalType)
Returns whether an evaluation type is allowed for this problem.
|
boolean |
isAllowedResponseEvalType(AnswerEvalType evalType)
Returns whether an evaluation type is allowed for responses to this problem.
|
static boolean |
isAnswerable(java.lang.String s) |
boolean |
isCorrect(R response)
A convenience method for isCorrect that uses the problem's properties as arguments
|
abstract boolean |
isCorrect(R response,
java.util.Map<java.lang.String,java.lang.String> args)
Determines whether the submitted response to this question is correct.
|
boolean |
isCorrectDefault(R response,
java.util.Map<java.lang.String,java.lang.String> args)
Provides a default implementation for use by
isCorrect(StudentResponseValue) . |
boolean |
isScrambleAnswers()
Returns whether answers are to be returned in a random order.
|
protected abstract R |
makeResponse(java.lang.String... responseParts)
For the given values entered, returns an AnswerValue containing those parts.
|
protected static void |
registerProblemTypeAttributes(java.lang.String problemClassType,
java.lang.String answerClassType,
java.lang.String answerValueClassType,
java.lang.String responseValueClassType,
int maxAnswers,
int answerValuePartsCount,
int responseValuePartsCount,
java.util.List<AnswerEvalType> allowedAnswerEvalTypes,
java.util.List<AnswerEvalType> allowedRepsonseEvalTypes)
Called by an AnswerableProblem subclass to register properties about that problem type
represented by that subclass.
|
void |
setAnswerEvalTypes(AnswerEvalType... evalTypes)
Specifies the evaluation type for each value part in this problem answers.
|
void |
setAnswerEvalTypes(AnswerEvalType evalType)
Sets all answer value parts to the same, single evaluation type.
|
void |
setAnswers(java.util.List<A> answers)
Specifies a set of answers to this problem.
|
void |
setCorrectAnswer(A answer)
Records the known correct answer that matches a student's response.
|
void |
setProperties(java.util.Map<java.lang.String,java.lang.String> properties) |
void |
setResponseEvalTypes(AnswerEvalType... evalTypes)
Specifies the evaluation type for each value part in this problem response.
|
void |
setResponseEvalTypes(AnswerEvalType evalType)
Sets all response value parts to the same, single evaluation type.
|
void |
setScrambleAnswers(boolean scrambleAnswers)
Specifies whether answers are to be displayed in a random order.
|
void |
setWrongAnswer(A answer)
Records the known wrong answer that matches a student's response.
|
void |
validate()
Check this object's data for inconsistencies
Subclasses must (by informal contract) implement this method!
|
getQuestion, getResponseTypeDescription, setQuestion
initializeContent
getDescription, getName, setDescription, setName
createKey, createKey, equals, extractType, extractUID, getKey, getShortKey, getShortKey, getType, getUID, getValidatorUtilities, setValidatorUtilities, toString, validateKey
protected static java.util.List<java.lang.String> answerableClasses
protected java.util.Map<java.lang.String,java.lang.String> probProperties
protected AnswerableProblem(java.lang.String classType, java.lang.String uid)
classType
- The fully-specified class name of the concrete subclass. This is equivalent to
the String returned from calling <Class>.class.getName()uid
- A unique identifier for this object instance.protected AnswerableProblem(java.lang.String classType, java.lang.String uid, java.lang.String name, java.lang.String description)
protected abstract A addAnswer(java.lang.String uid, boolean isCorrect, P... valueParts)
Answer
containing
those values.
It is highly recommended that each AnswerableProblem subclass implements a custom, public version of the addAnswer(...) method which hides from the calling code the variable arguments AnswerValuePart... valueParts.
For example, a fill in problem would implement:
public FillInAnswer addAnswer(String uid, boolean isCorrect, String value)
The implementation of this abstract method would be responsible for obtaining the evaluation type, and the subsequent creation of an AnswerValuePart.
uid
- Unique identifier for this answervalueParts
- The value parts comprising this answer.getAnswerEvalTypes()
,
AnswerValuePart
protected abstract R makeResponse(java.lang.String... responseParts)
responseParts
- One or more values entered in response to a question. For example, a fill-in
problem expects a single response string. An ordered pair problem expects two response strings.protected static void registerProblemTypeAttributes(java.lang.String problemClassType, java.lang.String answerClassType, java.lang.String answerValueClassType, java.lang.String responseValueClassType, int maxAnswers, int answerValuePartsCount, int responseValuePartsCount, java.util.List<AnswerEvalType> allowedAnswerEvalTypes, java.util.List<AnswerEvalType> allowedRepsonseEvalTypes) throws java.lang.IllegalStateException
problemClassType
- The fully specified AnswerableProblem subclass class name.answerValueClassType
- The fully specified AnswerValue subclass class name implementing
problemClassType's answer values.responseValueClassType
- The fully specified AnswerValue subclass class name implementing
problemClassType's student response values.maxAnswers
- Maximum number of answers allowed per problem instance. 0 signals to
allow an unlimited number of answers.answerValuePartsCount
- The number of value parts (AnswerValuePart) making up an answer
for answerValueClassType.responseValuePartsCount
- The number of value parts (AnswerValuePart) making up an answer
for responseValueClassType.allowedAnswerEvalTypes
- The list of evaluation types allowed in each answer's value parts.allowedRepsonseEvalTypes
- The list of evaluation types allowed in each response's value parts.java.lang.IllegalStateException
- if the subclass has already registered with different attribute valuesAnswerValue
,
AnswerValuePart
,
AnswerEvalType.getSupportedEvalTypes()
public static AnswerableProblemAttributes getProblemTypeAttributes(java.lang.String classType) throws java.lang.IllegalStateException
classType
- The fully specified AnswerableProblem subclass class name of interest.java.lang.IllegalStateException
- If classType was not previously registered.java.lang.IllegalStateException
- If the subclass failed to call
registerProblemTypeAttributes(String, String, String, String, int, int, int, List, List)
registerProblemTypeAttributes(String, String, String, String, int, int, int, List, List)
,
getProblemTypeAttributes()
public java.lang.String getUnscrambledAnswerIndexes(java.lang.String responseIndexes)
Certain problem types display multiple possible answers. This includes problem types that require a student to choose N correct answers and those that require a student to sort a list of displayed values.
For those problem types, ASSISTments determines correctness by comparing the answer index numbers selected by the student to those of the displayed answers.
For logging purposes ASSISTments needs to record the student's response (i.e. the index numbers) as if the answers had not be displayed in a random order.
Calling getUnscrambledAnswerIndexes() returns non-random answer index numbers corresponding to the student's response.
responseIndexes
- A space-separated list of answer indexes. For example, if
the problem displayed 3 answers and the student selected the first and the third,
responseIndexes would contain, as a string: 0 2Util.scrambleList(List)
,
getAnswers(boolean)
protected java.lang.String getUnscrambledAnswerIndexes(java.lang.String responseIndexes, boolean sort)
getUnscrambledAnswerIndexes(String)
for the method description.
This version of the method is intended for use by JUnit tests only.
responseIndexes
- A space-separated list of answer indexes. For example, if
the problem displayed 3 answers and the student selected the first and the third,
responseIndexes would contain, as a string: 0 2sort
- true to numerically sort the indexes in the return value; false
to leave them unsorted.getUnscrambledAnswerIndexes(String)
,
Util.scrambleList(List)
,
getAnswers(boolean)
public AnswerableProblemAttributes getProblemTypeAttributes() throws java.lang.IllegalStateException
java.lang.IllegalStateException
- @throws IllegalStateException If this instance's class type was not
previously registered.registerProblemTypeAttributes(String, String, String, String, int, int, int, List, List)
,
getProblemTypeAttributes(String)
public boolean isScrambleAnswers()
setScrambleAnswers(boolean)
,
getAnswers(boolean)
public void setScrambleAnswers(boolean scrambleAnswers)
scrambleAnswers
- true to return answers in a random order.getAnswers(boolean)
public void setAnswerEvalTypes(AnswerEvalType evalType)
For an answer with a single value part, specifies that part's evaluation type. For an answer with a multiple value parts, specifies a single evaluation type for all the value parts.
This is a convenience method to handle the case when you want all of a problem's answer value parts to use same evaluation type - or if there is only one value part per answer. Using this method allows you to pass a single AnswerEvalType rather than having to construct the list parameter needed for the other versions of the setEvalTypesPerAnswer() method.
evalType
- The evaluation type to apply to the answer's value part(s).setAnswerEvalTypes(AnswerEvalType...)
,
getAnswerEvalTypes()
,
getAnswerEvalType(int)
public void setAnswerEvalTypes(AnswerEvalType... evalTypes) throws java.lang.IllegalArgumentException
evalTypes
- The list of evaluation types in the exact order you want them applied to each
answer value part.
For example: For a problem type with two value parts, the first a number, the second a string, your
call would be:
setAnswerEvalTypes(AnswerEvalType.NUMERIC, AnswerEvalType.STRING)
or you could set-up and pass a 2-element AnswerEvalType array with the [0]
contains AnswerEvalType.NUMERIC and the [1]
contains AnswerEvalType.STRING
java.lang.IllegalArgumentException
- if the number of evalTypes is different than the
expected number for answers of this problem type.setAnswerEvalTypes(AnswerEvalType)
,
getAnswerEvalTypes()
,
getAnswerEvalType(int)
public java.util.List<AnswerEvalType> getAnswerEvalTypes()
public AnswerEvalType getAnswerEvalType(int i) throws java.lang.IllegalArgumentException
i
- index of the desired evaluation type.java.lang.IllegalArgumentException
- If the value of i exceeds the number of value parts per
answer for this problem type.getAnswerEvalTypes()
,
setAnswerEvalTypes(AnswerEvalType...)
,
setAnswerEvalTypes(AnswerEvalType)
public boolean isAllowedAnswerEvalType(AnswerEvalType evalType)
evalType
- Evaluation type to checkAnswerEvalType
,
registerProblemTypeAttributes(String, String, String, String, int, int, int, List, List)
public void setResponseEvalTypes(AnswerEvalType evalType)
For a response with a single value part, specifies that part's evaluation type. For a response with a multiple value parts, specifies a single evaluation type for all the value parts.
This is a convenience method to handle the case when you want all of a problem's response value parts to use same evaluation type - or if there is only one value part per response. Using this method allows you to pass a single AnswerEvalType rather than having to construct the list parameter needed for the other versions of the setEvalTypesPerAnswer() method.
evalType
- The evaluation type to apply to the response's value part(s).setResponseEvalTypes(AnswerEvalType...)
,
getResponseEvalTypes()
,
getResponseEvalType(int)
public void setResponseEvalTypes(AnswerEvalType... evalTypes) throws java.lang.IllegalArgumentException
evalTypes
- The list of evaluation types in the exact order you want them applied to each
response value part.java.lang.IllegalArgumentException
- if the number of evalTypes is different than the
expected number for responses of this problem type.setResponseEvalTypes(AnswerEvalType)
,
getResponseEvalTypes()
,
getResponseEvalType(int)
public java.util.List<AnswerEvalType> getResponseEvalTypes()
public AnswerEvalType getResponseEvalType(int i) throws java.lang.IllegalArgumentException
i
- index of the desired evaluation type.java.lang.IllegalArgumentException
- If the value of i exceeds the number of value parts per
response for this problem type.getResponseEvalTypes()
,
setResponseEvalTypes(AnswerEvalType...)
,
setResponseEvalTypes(AnswerEvalType)
public boolean isAllowedResponseEvalType(AnswerEvalType evalType)
evalType
- Evaluation type to checkAnswerEvalType
,
registerProblemTypeAttributes(String, String, String, String, int, int, int, List, List)
public int getAnswerCount()
public java.util.List<A> getAnswers(boolean enableRandom)
enableRandom
- Specifies whether to apply or ignore this problem's setting for
randomly ordering answers as follows:
setScrambleAnswers(boolean)
setting.
This means that if isScrambleAnswers()
returns false,
getAnswers returns the
answers in the order they were entered for this problem. That is to say: when
isScrambleAnswers()
returns false, the value of enableRandom
is irrelevant.
setScrambleAnswers(boolean)
setting and returns the
answers in the order they were entered for this problem.
getAnswers()
,
getAnswer(int)
,
setAnswers(List)
,
addAnswer(Answer)
,
addAnswer(String, boolean, AnswerValuePart...)
public java.util.List<A> getAnswers()
getAnswers(boolean)
,
getAnswer(int)
public A getAnswer(int index)
index
- Index of the answer to return.public void setAnswers(java.util.List<A> answers) throws java.lang.IllegalArgumentException
answers
- Answers to save.java.lang.IllegalArgumentException
addAnswer(Answer)
,
addAnswer(String, boolean, AnswerValuePart...)
,
getAnswers(boolean)
public void addAnswer(A answer) throws java.lang.IllegalArgumentException
answer
- Answer to addjava.lang.IllegalArgumentException
- If adding answer would exceed the maximum answers
allowed for this problem type.addAnswer(String, boolean, AnswerValuePart...)
,
setAnswers(List)
,
getAnswers(boolean)
,
getAnswer(int)
public abstract boolean isCorrect(R response, java.util.Map<java.lang.String,java.lang.String> args)
If your problem type defines correctness as when the student's response matches any one of
the answers marked as a correct answer, then your subclass can implement this method to call
#isCorrectDefault(StudentResponseValue)
.
If your problem type requires a different semantic for determining correctness, you must
implement that logic yourself in isCorrect(). In this case, your implementation
may want (or need) to call setWrongAnswer(Answer)
so that a common wrong answer
is available later to a caller.
response
- The student's response to compare to this problem's answer list.args
- Map for problem specific properties that will
be used in calculating correctness for the problem.#isCorrectDefault(StudentResponseValue)
,
setWrongAnswer(Answer)
public boolean isCorrect(R response)
public boolean isCorrectDefault(R response, java.util.Map<java.lang.String,java.lang.String> args)
isCorrect(StudentResponseValue)
.
response
- The student's responseargs
- problem specific propertiesAnswer#isMatch(StudentResponseValue)
.isCorrect(StudentResponseValue)
,
Answer#isCorrect(StudentResponseValue)
,
Answer#isMatch(StudentResponseValue)
,
Answer#isMatchDefault(StudentResponseValue)
public java.util.List<P> getAnswerValuePartsList() throws java.lang.IllegalStateException
getAnswerValuePartsList() expects that each answer contains exactly one value part.
Use this method when you might need to sort an answer list's values.
java.lang.IllegalStateException
- if this problems answers have more than 1 value part per answergetProblemTypeAttributes()
public void setWrongAnswer(A answer)
Answer#isMatch(StudentResponseValue)
,
getWrongAnswer()
public A getWrongAnswer()
setWrongAnswer(Answer)
public void setCorrectAnswer(A answer)
Answer#isMatch(StudentResponseValue)
,
getCorrectAnswer()
public A getCorrectAnswer()
setCorrectAnswer(Answer)
public void validate() throws ValidationException
SelfValidating
validate
in interface SelfValidating
validate
in class Problem
ValidationException
- if the data is invalidpublic static boolean isAnswerable(java.lang.String s)
public void addProperty(java.lang.String key, java.lang.String value)
public java.lang.String getProperty(java.lang.String key)
public java.util.Map<java.lang.String,java.lang.String> getProperties()
public void setProperties(java.util.Map<java.lang.String,java.lang.String> properties)