Skip to content

Commit

Permalink
Refactor swapper to use new mc
Browse files Browse the repository at this point in the history
  • Loading branch information
leung018 committed Oct 26, 2023
1 parent 8c6fb2d commit db43bb1
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 50 deletions.
27 changes: 25 additions & 2 deletions src/model/mc.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,18 +67,41 @@ export class MultipleChoiceError extends CustomBaseError {
// TODO: Migrate MultipleChoice to NewVersionMultipleChoice
export class NewVersionMultipleChoice {
choices: ReadonlyArray<{
description: string
answer: string
isFixedPosition: boolean
}>

correctChoiceIndex: number

static createWithNoFixedChoices({
answers = ['a', 'b'],
correctAnswerIndex = 0,
} = {}): NewVersionMultipleChoice {
return new NewVersionMultipleChoice({
choices: answers.map((answer) => ({
answer,
isFixedPosition: false,
})),
correctChoiceIndex: correctAnswerIndex,
})
}

static createTestInstance({
choices = [
{ answer: 'a', isFixedPosition: false },
{ answer: 'b', isFixedPosition: false },
],
correctChoiceIndex = 0,
}): NewVersionMultipleChoice {
return new NewVersionMultipleChoice({ choices, correctChoiceIndex })
}

constructor({
choices,
correctChoiceIndex,
}: {
choices: ReadonlyArray<{
description: string
answer: string
isFixedPosition: boolean
}>
correctChoiceIndex: number
Expand Down
95 changes: 49 additions & 46 deletions src/model/swap.test.ts
Original file line number Diff line number Diff line change
@@ -1,82 +1,85 @@
import { MultipleChoiceSwapper } from './swap'
import { MultipleChoice } from './mc'
import { MultipleChoice, NewVersionMultipleChoice } from './mc'

describe('MultipleChoiceSwapper.getSignificantlySwapped', () => {
it('should compute swaps for two choices', () => {
const mc = new MultipleChoice({
choices: ['a', 'b'],
correctChoiceIndex: 0,
const mc = NewVersionMultipleChoice.createWithNoFixedChoices({
answers: ['a', 'b'],
correctAnswerIndex: 0,
})
expect(MultipleChoiceSwapper.getSignificantlySwapped(mc)).toEqual(
new Set([
new MultipleChoice({ choices: ['b', 'a'], correctChoiceIndex: 1 }),
NewVersionMultipleChoice.createWithNoFixedChoices({
answers: ['b', 'a'],
correctAnswerIndex: 1,
}),
]),
)
})

it('should compute swaps for three choices', () => {
const mc = new MultipleChoice({
choices: ['a', 'b', 'c'],
correctChoiceIndex: 1,
const mc = NewVersionMultipleChoice.createWithNoFixedChoices({
answers: ['a', 'b', 'c'],
correctAnswerIndex: 1,
})
expect(MultipleChoiceSwapper.getSignificantlySwapped(mc)).toEqual(
new Set([
new MultipleChoice({ choices: ['b', 'c', 'a'], correctChoiceIndex: 0 }),
new MultipleChoice({ choices: ['c', 'a', 'b'], correctChoiceIndex: 2 }),
NewVersionMultipleChoice.createWithNoFixedChoices({
answers: ['b', 'c', 'a'],
correctAnswerIndex: 0,
}),
NewVersionMultipleChoice.createWithNoFixedChoices({
answers: ['c', 'a', 'b'],
correctAnswerIndex: 2,
}),
]),
)
})

it('should further limit output when lockedChoiceIndices have one element', () => {
const mc = new MultipleChoice({
choices: ['Apple only', 'Banana only', 'None of the above'],
it('should further limit output when one choice is fixed position', () => {
const mc = new NewVersionMultipleChoice({
choices: [
{ answer: 'Apple only', isFixedPosition: false },
{ answer: 'Banana only', isFixedPosition: false },
{ answer: 'None of the above', isFixedPosition: true },
],
correctChoiceIndex: 1,
})
const lockedChoiceIndices = new Set([2])
expect(
MultipleChoiceSwapper.getSignificantlySwapped(mc, lockedChoiceIndices),
).toEqual(
expect(MultipleChoiceSwapper.getSignificantlySwapped(mc)).toEqual(
new Set([
new MultipleChoice({
choices: ['Banana only', 'Apple only', 'None of the above'],
new NewVersionMultipleChoice({
choices: [
{ answer: 'Banana only', isFixedPosition: false },
{ answer: 'Apple only', isFixedPosition: false },
{ answer: 'None of the above', isFixedPosition: true },
],
correctChoiceIndex: 0,
}),
]),
)
})

it('should ignore lockedChoiceIndices when they are out of range', () => {
const mc = new MultipleChoice({
choices: ['a', 'b'],
correctChoiceIndex: 1,
})
const lockedChoiceIndices = new Set([2])
expect(
MultipleChoiceSwapper.getSignificantlySwapped(mc, lockedChoiceIndices),
).toEqual(
new Set([
new MultipleChoice({ choices: ['b', 'a'], correctChoiceIndex: 0 }),
]),
)
})

it('should return same set when lockedChoiceIndices contain all choices', () => {
const mc = MultipleChoice.createTestInstance({
choices: ['a', 'b', 'c'],
const mc = NewVersionMultipleChoice.createTestInstance({
choices: [
{ answer: 'a', isFixedPosition: true },
{ answer: 'b', isFixedPosition: true },
{ answer: 'c', isFixedPosition: true },
],
})
const lockedChoiceIndices = new Set([0, 1, 2])
expect(
MultipleChoiceSwapper.getSignificantlySwapped(mc, lockedChoiceIndices),
).toEqual(new Set([mc]))
expect(MultipleChoiceSwapper.getSignificantlySwapped(mc)).toEqual(
new Set([mc]),
)
})

it('should return empty set when lockedChoiceIndices contain all choices except one', () => {
const mc = MultipleChoice.createTestInstance({
choices: ['a', 'b', 'c'],
const mc = NewVersionMultipleChoice.createTestInstance({
choices: [
{ answer: 'a', isFixedPosition: true },
{ answer: 'b', isFixedPosition: true },
{ answer: 'c', isFixedPosition: false },
],
})
const lockedChoiceIndices = new Set([0, 1])
expect(
MultipleChoiceSwapper.getSignificantlySwapped(mc, lockedChoiceIndices),
).toEqual(new Set())
expect(MultipleChoiceSwapper.getSignificantlySwapped(mc)).toEqual(new Set())
})
})
36 changes: 34 additions & 2 deletions src/model/swap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
import { getPermutations } from '../utils/permutation'
import { MultipleChoice } from './mc'
import { MultipleChoice, NewVersionMultipleChoice } from './mc'

export class MultipleChoiceSwapper {
static getSignificantlySwapped(
originalMc: NewVersionMultipleChoice,
): Set<NewVersionMultipleChoice> {
const oldVersionOriginalMc = new MultipleChoice({
choices: originalMc.choices.map((c) => c.answer),
correctChoiceIndex: originalMc.correctChoiceIndex,
})
const lockedChoiceIndices = new Set<number>(
originalMc.choices
.map((c, i) => (c.isFixedPosition ? i : null))
.filter((i) => i !== null) as number[],
)
const swappedMCs = new MultipleChoiceSwapper(
oldVersionOriginalMc,
lockedChoiceIndices,
).getSignificantlySwapped()
return new Set(
Array.from(swappedMCs).map((mc) => {
const newChoices = mc.choices.map((choice) => ({
answer: choice,
isFixedPosition: lockedChoiceIndices.has(
oldVersionOriginalMc.choices.findIndex((e) => e == choice),
),
}))
return new NewVersionMultipleChoice({
choices: newChoices,
correctChoiceIndex: mc.correctChoiceIndex,
})
}),
)
}

/**
* Computes a set of MultipleChoice objects where the choices are significantly swapped.
* Significantly swapped means that each choice is in a different position from the original MultipleChoice object,
Expand All @@ -11,7 +43,7 @@ export class MultipleChoiceSwapper {
* @param lockedChoiceIndices A set of indices of choices that are locked and should not be swapped.
* @returns A set of MultipleChoice objects where the choices are significantly swapped.
*/
static getSignificantlySwapped(
static oldGetSignificantlySwapped(
originalMc: MultipleChoice,
lockedChoiceIndices: ReadonlySet<number> = new Set(),
): Set<MultipleChoice> {
Expand Down

0 comments on commit db43bb1

Please sign in to comment.