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.
partners(padme, anakin).
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 kris_kringle/3
facts
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.