Create a Secret Santa application using Intrexx (Part 2)
In a previous blog post, we explored how we can quickly develop the application structure for a Secret Santa app using Intrexx that would allow people to create an event, set a budget/location and add participants.
Of course, for the application to be fully functional, we need to provide the user with a function to draw names for each participant. This blog post will showcase how we can achieve this goal and finish our Secret Santa app. Let’s get started!
1. Derangements
To start, we know that our algorithm should receive a list of participants and shuffle this list according to these rules:
- No participant should draw their own name
- No participant should have more than one drawn person (draw multiple names)
- No participant should have no drawn person (draw no names)
- All participants should be drawn (no person without a gift!)
We can identify that what we are looking for is a permutation of our list. Not just any permutation, but a permutation where no element appears in the same position as it was in the original list. More mathematically specific: a derangement. This might sound not very easy at first, but maybe we can make it more clear with an example:
For a list of participants: {Alice, Bob, Charlie}
, the only allowed recipients lists are: {Bob, Charlie, Alice}
and {Charlie, Alice, Bob}
.
We are looking for these permutations (derangements), and now, the only thing left to do is find a way to generate these permutations randomly.
2. Shuffle method
To randomly shuffle a list in Groovy, we can call the shuffle method in our list to randomly reorder our elements (participants). Shuffle is designed to produce a different randomized order every time it is called (with high probability). If you used the same list of participants for multiple events, you would most likely have a different list each time. You can find more information about the shuffle method here.
Now, we have both pieces of our puzzle. We know how to shuffle a list of participants randomly, and we know how a valid list of recipients should look like. An easy solution: repeatedly shuffle our original list and check if our new order is a derangement. Of course, mathematical algorithms are specifically designed to generate derangements more efficiently, but we will implement this approach for simplicity.
Let’s check a code example:
def shuffleWithoutRepetition(list) {
// Only shuffle lists that have more than 1 participant
if(list.size() > 1){
// Clone our initial list for comparison
def shuffledList = list.clone()
// Continue to shuffle until we find our derangement
while (true) {
shuffledList.shuffle()
// Check if any element is in the same position
def anySamePosition = list.indices.any { index ->
list[index] == shuffledList[index]
}
// If no element is in the same position, break the loop
if (!anySamePosition) {
break
}
}
return shuffledList
}
else{
return null
}
}
In this example, we have created the function shuffleWithoutRepetition() that receives a list as a parameter and returns a random derangement of our list. Remember that lists with less than two items do not have a derangement.
3. Intrexx implementation
We are almost done, the only thing left is to create our Intrexx process. Start by creating a process called “Secret Santa Process” and add an event handler for datagroup “Event” on Edit. Afterward, link to our event handler a Groovy script action event. Your process should look like this:
In the Groovy script action, get the participants of your event using a select statement and add the ids to a list. You can then use the function shuffleWithoutRepetition to generate your recipients' list and add them individually to each participant. The complete Groovy script should look like this:
import de.uplanet.scripting.groovy.util.Safely
def conn = g_dbConnections.systemConnection
def stmt = null
def rs = null
def eventSTRID = g_record["< Your PK guid from Event data group >"].value /* datafield (PK) (S) ID */
def participantsList = []
try {
stmt = g_dbQuery.prepare(conn, "SELECT < Your User ID reference from Participant data group > FROM DATAGROUP('< Your Participant data group GUID >') WHERE FKSTRID = ?")
stmt.setString(1, eventSTRID)
rs = stmt.executeQuery()
while (rs.next())
{
participantsList.add(rs.getIntValue(1))
}
rs = Safely.close(rs)
stmt = Safely.close(stmt)
}
finally {
rs = Safely.close(rs) stmt = Safely.close(stmt)
}
def shuffleWithoutRepetition(list) {
// Only shuffle lists that have more than 1 participant
if(list.size() > 1) {
//Clone our initial list to use for comparison
def shuffledList = list.clone()
// Continue to shuffle until we find our derangement
while (true) {
shuffledList.shuffle()
// Check if any element is in the same position
def anySamePosition = list.indices.any { index ->
list[index] == shuffledList[index]
}
// If no element is in the same position, break the loop
if (!anySamePosition) {
break
}
}
return shuffledList
}
else {
return null
}
}
if(participantsList.size() > 1) {
recipientsList = shuffleWithoutRepetition(participantsList)
for(int i = 0; i< participantsList.size(); i++) {
try {
stmt = g_dbQuery.prepare(conn, "UPDATE DATAGROUP('< Your Participant data group GUID >') SET < Recipient reference > = ? WHERE < User reference > = ? AND FKSTRID = ?")
stmt.setInt(1, recipientsList[i])
stmt.setInt(2, participantsList[i])
stmt.setString(3, eventSTRID)
stmt.executeUpdate()
stmt = Safely.close(stmt)
}
finally {
stmt = Safely.close(stmt)
}
}
}
Finish by publishing the process, and you are done. Every time a user edits an event, each participant will receive a random recipient. You can extend this further by adding an email action to each participant to inform them of their drawn name or just simply display the list in the app.
Good luck in building your creative ideas!