Kris Kringle Assignment Tool
Each year, My family likes to have a Kris Kringle arrangement for Christmas rather than everyone buying everyone else a present. It is cheaper and easier on everyone and we still share the giving nature of the holiday. It works like this: each person is assigned one other person to buy a present for, and then there is a limit on the amount that can be spent on the present, to ensure that people don't try to out compete each other. I am the one that is tasked to assign buyers to targets so I wrote a little program to help me.
There are a few rules that need to be considered.
- Each person is assigned one other person to buy a present for.
- A person will not buy for themselves.
- People should buy for someone different each year.
- Partners should not buy for one another.
To demonstrate the program, I've changed the names to Star Wars characters.
person('obi-wan'). person(r2d2). person(c3po). person(anakin). person(padme). person(yoda).
There is also one couple, who will never buy for one another.
Each year, a record is kept of who bought for whom and stored in the
kris_kringle/3 predicate along with the associated year.
kris_kringle(Buys, For) :- kris_kringle(Buys, For, Year), \+ (kris_kringle(Buys, _, Y2), Y2 > Year). kris_kringle(_,_,_) :- fail.
As this would be the first year, a placeholder is required for
kris_kringle/3 which means
there are no current combinations.
Next a predicate to determine valid combinations.
valid_to_buy_for(P, X) :- dif(P, X), \+ partners(P, X), \+ partners(X, P), \+ kris_kringle(P, X, _).
Given a list of people, the
buying_for/3 predicate will generate valid combinations for all
posibilities of who can buy for each other.
buying_for(, , ). buying_for([P|T], Remaining, [[P,X]|Rest]) :- select(X, Remaining, MorePeeps), valid_to_buy_for(P,X), buying_for(T, MorePeeps, Rest).
Now, to actually generate a random list of buyers and targets, find all the possible sets for buy/target pairs and take a random set as the result.
generate_kk_list(Result) :- findall(P, person(P), People), findall(Buying, buying_for(People, People, Buying), AllBuying), random_member(Result, AllBuying).
That's it for the program, now if it is run, something like the following happens:
?- generate_kk_list(R). R = [['obi-wan', c3po], [r2d2, 'obi-wan'], [c3po, padme], [anakin, r2d2], [padme, yoda], [yoda, anakin]].
This a list of buyers and targets for the year, which can now be stored as
for next year so that everyone gets a different target.
buys_for('obi-wan', padme, 2020). buys_for(r2d2, anakin, 2020). buys_for(c3po, yoda, 2020). buys_for(anakin, c3po, 2020). buys_for(padme, 'obi-wan', 2020). buys_for(yoda, r2d2, 2020).
Great, all done.
By the way, I've been using this for five years now, and it is a bit clunky but becuase I only need it once a year then there is no need to spend time on improvements.