B u s L o g i c:- prolog, code, chatter.

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.

  1. Each person is assigned one other person to buy a present for.
  2. A person will not buy for themselves.
  3. People should buy for someone different each year.
  4. 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.