Skip to content

Commit

Permalink
Merge pull request #335 from gloggi/choose-group-size-for-individual-…
Browse files Browse the repository at this point in the history
…participants

Choose group size for individual participants
  • Loading branch information
carlobeltrame authored Mar 8, 2024
2 parents e9814b7 + 469be75 commit 0a56308
Show file tree
Hide file tree
Showing 6 changed files with 88 additions and 12 deletions.
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ indent_style = tab
tab_width = 4
trim_trailing_whitespace = true

[{*.vue, *.js}]
[*.{vue,js}]
indent_size = 2
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

##### März 2024
- Nachdem man via Blöcke-Ansicht eine Beobachtung hinzugefügt hat, bleibt man nun auf dem Beobachtungs-Formular, um noch weitere Beobachtungen erfassen zu können. Das vorherige Verhalten war noch aus der Zeit als der Spick und die Blöcke-Ansicht noch separat waren, und war für Beobachtungsaufträge mit genau einer Beobachtung pro Auftrag optimiert. Das neue Verhalten ist hoffentlich hilfreicher, um mehrere kleine Beobachtungen zu erfassen [#334](https://github.com/gloggi/qualix/pull/334)
- Im TN-Gruppen-Generator kann man nun angeben, wenn einzelne TN besser in eine grössere oder eine kleinere Gruppe eingeteilt werden sollen. Dies kann zum Beispiel nützlich sein, wenn TN nicht die ganze Zeit über im Kurs anwesend sein können [#335](https://github.com/gloggi/qualix/pull/335)

##### Februar 2024
- Qualix enthält neu ein Namenslernspiel! Auf der TN-Liste hat es einen Link zum Spiel [#332](https://github.com/gloggi/qualix/pull/332)
Expand Down
2 changes: 2 additions & 0 deletions lang/de/t.php
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,8 @@
"name" => "Bezeichnung",
"of_size" => "Gruppen mit je :size TN",
"of_size_between" => "Gruppen mit je :min-:max TN",
"prefer_large_group" => ":sizeer-Gruppe bevorzugen",
"prefer_small_group" => ":sizeer-Gruppe bevorzugen",
),
"validation_errors" => "Die TN-Gruppen konnten nicht gespeichert werden. Bitte korrigiere deine Eingaben und versuche es dann erneut."
),
Expand Down
61 changes: 53 additions & 8 deletions resources/js/components/participantGroups/InputGroupSplit.vue
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,27 @@
narrow-form
:require-multiple="$t('t.views.admin.participant_group_generator.select_multiple_participants')"
multiple></input-multi-multi-select>

<input-multi-select
v-if="unevenSplit"
:name="`${name}[preferLargeGroup]`"
v-model="currentValue.preferLargeGroup"
:label="$t('t.views.admin.participant_group_generator.split.prefer_large_group', { size: largeGroupSize })"
label-class="col-12"
:options="participantsWithoutSmallGroupPreference"
:display-field="anyDuplicateMembershipGroups ? 'name_and_group' : 'scout_name'"
narrow-form
multiple></input-multi-select>

<input-multi-select
v-if="unevenSplit"
:name="`${name}[preferSmallGroup]`"
v-model="currentValue.preferSmallGroup"
:label="$t('t.views.admin.participant_group_generator.split.prefer_small_group', { size: smallGroupSize })"
:options="participantsWithoutLargeGroupPreference"
:display-field="anyDuplicateMembershipGroups ? 'name_and_group' : 'scout_name'"
narrow-form
multiple></input-multi-select>
</b-collapse>
</b-card>
</template>
Expand All @@ -78,11 +99,12 @@ import {kebabCase} from 'lodash'
import InputText from '../form/InputText'
import RowText from '../form/RowText'
import InputCheckbox from '../form/InputCheckbox'
import InputMultiSelect from '../form/InputMultiSelect.vue';
export default {
name: 'InputGroupSplit',
mixins: [ Input ],
components: {InputCheckbox, RowText, InputText, InputMultiMultiSelect },
components: { InputMultiSelect, InputCheckbox, RowText, InputText, InputMultiMultiSelect },
props: {
value: { type: Object, default: () => ({}) },
participants: { type: Array, required: true },
Expand All @@ -93,21 +115,44 @@ export default {
numParticipants() {
return this.participants.length
},
largeGroupSize() {
if (!validSplitGroups(this.currentValue, this.numParticipants)) {
return 0
}
return Math.ceil(this.numParticipants / parseInt(this.currentValue.groups))
},
smallGroupSize() {
if (!validSplitGroups(this.currentValue, this.numParticipants)) {
return 0
}
return Math.floor(this.numParticipants / parseInt(this.currentValue.groups))
},
unevenSplit() {
if (!validSplitGroups(this.currentValue, this.numParticipants)) {
return false
}
return this.largeGroupSize !== this.smallGroupSize
},
groupSizeText() {
if (!validSplitGroups(this.currentValue, this.numParticipants)) {
return this.$t('t.views.admin.participant_group_generator.split.enter_number_of_groups')
}
const groups = parseInt(this.currentValue.groups)
const min = Math.floor(this.numParticipants / groups)
const max = Math.ceil(this.numParticipants / groups)
if (min === max) {
return this.$t('t.views.admin.participant_group_generator.split.of_size', { size: min })
if (this.smallGroupSize === this.largeGroupSize) {
return this.$t('t.views.admin.participant_group_generator.split.of_size', { size: this.smallGroupSize })
}
return this.$t('t.views.admin.participant_group_generator.split.of_size_between', { min, max })
return this.$t('t.views.admin.participant_group_generator.split.of_size_between', { min: this.smallGroupSize, max: this.largeGroupSize })
},
conditionsId() {
return kebabCase(`collapse-${this.name}-conditions`)
}
},
participantsWithoutSmallGroupPreference() {
const preferSmallGroup = this.currentValue.preferSmallGroup.split(',')
return this.participants.filter(participant => !preferSmallGroup.includes(`${participant.id}`))
},
participantsWithoutLargeGroupPreference() {
const preferLargeGroup = this.currentValue.preferLargeGroup.split(',')
return this.participants.filter(participant => !preferLargeGroup.includes(`${participant.id}`))
},
},
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,8 @@ export default {
forbidMembershipGroups: '0',
forbiddenPairings: [''],
encouragedPairings: [''],
preferLargeGroup: '',
preferSmallGroup: '',
}
},
addGroupSplit() {
Expand All @@ -191,6 +193,12 @@ export default {
.map(group => group.map(participant => this.participantToIndex(participant)).filter(index => index !== -1))
.filter(group => group.length > 1)
},
prepareSizePreference(participants, split) {
if (Math.ceil(this.selectedParticipants.length / split.groups) === Math.floor(this.selectedParticipants.length / split.groups)) {
return []
}
return participants.map(participant => this.participantToIndex(participant)).filter(index => index !== -1)
},
generate() {
this.progress = 0
this.inProgress = true
Expand All @@ -216,7 +224,9 @@ export default {
]),
encouragedPairings: this.preparePairings([
...split.encouragedPairings.map(pairing => pairing.split(',')),
])
]),
preferLargeGroup: this.prepareSizePreference(split.preferLargeGroup.split(','), split),
preferSmallGroup: this.prepareSizePreference(split.preferSmallGroup.split(','), split),
})),
})
},
Expand Down
22 changes: 20 additions & 2 deletions resources/js/components/participantGroups/geneticGolferSolver.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function geneticGolferSolver(numParticipants, roundSpecifications, onProgress) {
function potentialFor(group, weights) {
return -group.map(member => {
return weights[member]
.filter(index => !group.includes(index))
.filter((_, index) => !group.includes(index))
.reduce((sum, weight) => sum + Math.sign(weight) * Math.pow(weight, 2), 0)
}).reduce((sum, memberSum) => sum + memberSum, 0)
}
Expand Down Expand Up @@ -83,7 +83,7 @@ function geneticGolferSolver(numParticipants, roundSpecifications, onProgress) {
}
}

function createWeights({ groups, ofSize, forbiddenPairings: forbiddenPairs, discouragedPairings: discouragedGroups, encouragedPairings: encouragedPairs }) {
function createWeights({ groups, ofSize, forbiddenPairings: forbiddenPairs, discouragedPairings: discouragedGroups, encouragedPairings: encouragedPairs, preferLargeGroup, preferSmallGroup }) {
const totalSize = groups * ofSize
const weights = range(totalSize).map(() => range(totalSize).fill(0))

Expand Down Expand Up @@ -117,6 +117,24 @@ function geneticGolferSolver(numParticipants, roundSpecifications, onProgress) {
})
})

// Preference for a bigger group means pairing with an empty slot is a disadvantage
preferLargeGroup.forEach(participant => {
for (let emptySlot = numParticipants; emptySlot < totalSize; emptySlot++) {
weights[participant][emptySlot] = weights[emptySlot][participant] = 1
}
})

// Preference for a smaller group means pairing with an empty slot is an advantage
preferSmallGroup.forEach(participant => {
for (let emptySlot = numParticipants; emptySlot < totalSize; emptySlot++) {
weights[participant][emptySlot] = weights[emptySlot][participant] = -1
}
})

// Normalize so we only have zero or positive weights. The calculation of permutation potential works better this way
const minWeight = Math.min(0, ...weights.flatMap(w => w))
forEachPair(range(0, totalSize), (i, j) => weights[i][j] = weights[j][i] = weights[i][j] - minWeight)

return weights
}

Expand Down

0 comments on commit 0a56308

Please sign in to comment.