club3 is a program to schedule a series of meetings of the Toastmaster™ type fairly.


Download club3.tar.bz2 (version 20090823).

Unpack it to create a directory club3, which contains a shell script called club3. Put a copy of or soft link to club3 in your PATH.

The subdirectory lisp contains several Lisp files, the main file being club3.lisp. If you move club3.lisp to a different location, you must ensure that all the other .lisp files in the lisp subdirectory move with it, as code in club3.lisp assumes that they are in the same directory as itself.

The script club3 sets and uses an environment variable CLUB3DIR to identify the directory containing club3.lisp. Change it to suit you.

The script club3 also sets and uses an environment variable LISP to identify your Common Lisp system. Change to suit you.

*Optional:* You may compile club3.lisp (using the Common Lisp function compile-file). Only club3.lisp need be explicitly compiled — it will take care of compiling the other .lisp files.


In a directory that contains all the data relevant to your club, call club3 with the argument schedules or profiles.

The data in the current directory serves as input to both calls, and influences the result. This input data consists of an init file club3rc.lisp and a subdirectory past-schedules which contains the scheduling history of the club. For an example, see the directory examples/articulators in the distribution.

Generating schedules

% club3 schedules

generates schedules. (I.e., type club3 schedules at your Unix prompt %.)

The schedules consist of a .txt file for each date in the calendar (see below), and an HTML file new-schedules.html that lists the schedules as an easy-to-read and -print table. For a saved example, see examples/articulators/current-schedules.html.

The generated .txt files can be inserted into past-schedules (see below) to influence the course of future schedules (created by future calls to club3 schedules). Before putting a .txt file into past-schedules, it is a good idea to edit this file by hand to ensure that it accurately records the members who actually performed the roles (since club members typically negotiate substitutions up to the very last minute because of unforeseen circumstances).

Generating profiles

% club3 profiles

generates member profiles in a subdirectory called profiles, browsable via an index.html file. Example: examples/articulators/profiles/index.html.


The init file club3rc.lisp is essential. At the very least, it must use the operators members, lineups, and calendar to respectively list the members in the club; the type of meeting lineups used; and the calendar dates for which schedules are desired.

Other operators such as availability, expertise, eagerness, role-difficulty, and number-of-trials are optional: However it is useful to invoke some or all of them so that the generated schedules are both equitable and feasible.

The operators full-names, role-names, and club-name help make the output schedules more readable.

Example: examples/articulators/club3rc.lisp.


The operator members lists the members in the club, a symbol for each member. E.g.,

(members andrew brad charlie dan eileen fran george hilary ian judy
         kim lori marcia nelson oliver prasad rick stephanie theresa
         ulrich vanessa willy xavier yul zoe)


The operator lineups introduces the various prescribed lineups for the club’s meetings. Each lineup is a list whose first element is a keyword describing the type of meeting, and whose second element is a list of roles.

(lineups (:default
           (tm word joke poet speaker speaker speaker
               ge eval eval eval topics)))

Roles can repeat: the above lineup allows for three speakers and three evals.

The first lineup is considered the default lineup (even if it wasn’t explicitly named :default). lineups can take more arguments if different lineups are possible (for different meetings).

(lineups (:default ...)
          (tm word joke poet speaker speaker ge eval eval topics)))

This specifies a meeting of type :topics-tuesday, which differs from the :default meeting in that its lineup has only two speakers and two evals. (The purpose of this meeting is plausibly to allow more time for topics than is possible in a :default meeting.)

A type of meeting called :superset may be optionally added. This specifies the list of roles that will be specified in the HTML table generated by club3 schedules for a sequence of meetings, which may be of different type. The idea is to list all the roles possible in all the meetings, and have the column for each meeting fill only the row that is relevant to it.


The operator calendar contains a series of dates for which meetings need to be scheduled. Each date is given as a dotted integer YYYY.MM.DD; thus, 2009.02.24 is 2009 Feb 24.

(calendar 2009.03.03

If the type of meeting to be held on a particular date differs from the :default type of meeting, then the date is enclosed as the first element in a list whose second element is the type of meeting. E.g.

(calendar 2009.03.03
          (2009.03.10 :contest)
          (2009.03.31 :skills-night)
          (2009.04.07 :topics-tuesday))

The type of meeting is typically one of the keywords introduced by the lineups operator (see above). However, it can be anything. If it isn’t one of the keywords introduced by lineups, then no schedule is presumed for that date. (This is useful to mark certain dates for special activities that do not require scheduling, or have been scheduled by other means.)


The availability operator contains exceptional information about the temporal availability of the members. By default, members are assumed to be always available. To override this for any member, that member’s symbol is associated with an expression denoting when that member is available.

Availability expressions are given in prefix notation using operators such as after, always, before, between, from, on, and until whose arguments are dates or the results of other operations.

and, not, and or are also allowed, but can only operate on the results of other operations (i.e. not directly on dates). [Use on if you find yourself wanting to use and/not/or directly on dates. E.g., (not (on 2009.03.17)).]

Dates are either in YYYY.MM.DD format or the keyword :further-notice (= ∞). Examples:

(availabilty (kim (not (between 2009.03.10 2009.03.21))))

states that kim will not be available from March 3 to 21, 2009 (inclusive).

(availability (kim ...)
              (theresa (not (until :further-notice))))

says that theresa will not be available until further notice.

(availability (kim ...)
              (theresa ...)
              (kumaran (and (not (on 2009.02.24 2009.04.28
                                     2009.05.26 2009.06.30))
                            (not (between 2009.03.10

states that kumaran will not be available on Feb 24, April 28, May 26, and June 30 of 2009; and also between March 10 and 31 of 2009.


Using the role-difficulty operator, you can identify specific roles to be either :easy, :intermediate, or :hard. By default, all roles are considered to be :easy. Example entries:

(role-difficulty (speaker :intermediate)
                 (eval :hard))


By default, every member is considered to be capable of doing all and only the :easy roles To modify this, use the expertise operator. Each argument is a list whose first element is the member name and whose subsequent elements are the additional roles they can tackle beyond the :easy roles. E.g.,

(expertise (rick topics eval))

If you entered :intermediate or :hard instead of a role name, all roles that are intermediate or hard, respectively, are intended.

(expertise (rick ...)
           (connie :intermediate))

The keyword :all stands for *all* roles. Thus to encode the fact that bash can do anything:

(expertise (rick ...)
           (connie ...)
           (bash :all))

You may also enter a list such as (- speaker topics) instead of a role. This requests that the speaker and topics roles be removed from that member’s expertise.

(expertise (rick ...)
           (connie ...)
           (bash ...)
           (judyk (- speaker topics)))


By default, all members are supposed to exhibit the same moderate level of eagerness to be scheduled. However, using the eagerness operator, you can add an eagerness level to a member. Each argument consists of the member name followed by a number between 0 and 3 inclusive: 0 for inactive; 1 for taking it slow; 2 for normal (the default); and 3 for fast track. E.g.

(eagerness (joseph 3)
           (judyk 1)
           (theresa 0))

states that theresa is inactive, judyk wants to take it slow, and joseph wants the fast track.


club3 uses a system that ascribes demerits to each member for a role on any day (based on history, availability, etc). Since a lineup is filled in a certain order, earlier roles may beat out later roles in getting their best-suited members, leading to overall higher demerits. club3 tries to counter this by picking among eligible members using an geometrically decaying distribution among them, so a member with higher demerits could still snag a role. club3 then generates more, different lineups, to see if another lineup with fewer overall demerits materializes.

By default, club3 generates 400 trial lineups for each meeting date, and picks the best among them. club3 output will also indicate the ordinal n of the eventually picked lineup, so you get an idea if the number of trials is reasonable. (If n is too close to 400, and the best lineup was updated many times, perhaps more trials are needed.)

To change the number of trials, use the number-of-trials operator, e.g.,

(number-of-trials 200)


The symbol used for each member is typically capitalized when printing out schedules or profiles; thus, charlie becomes “Charlie” and marcia becomes “Marcia”. However, in certain cases, naive capitalization may be inaccurate or unsightly. For instance, if there are several members with the same (first) name, the members operator may have used an initial to disambiguate, e.g., judyk and judyp for two different Judys. To properly capitalize them in the output schedule (so the Judys don’t judge you), use the full-names operator:

(full-names (judyk “Judy K”)
            (judyp “Judy P”))

You may add more complicated names:

(full-names (judyk ...)
            (judyp ...)
            (kelvin “William Thomson, First Baron Kelvin”))

For routine scheduling, however, a first name, sometimes enhanced with a disambiguating initial, is usually enough.


lineups uses abbreviated symbols for each role. When printing out the schedule as an HTML file, the roles are capitalized, and if there are multiple instances of a role in a meeting, each role instance gets a number suffix (e.g. “Speaker #2”). If you want the printed name for a role to be different than the mere capitalization of the role symbol, you may use the role-names operator, e.g.

(role-names (tm “Toastmaster”)
            (eval “Evaluator”))


The name of the club is set using the operator club-name. This name is used to title the schedules.

(club-name “The Articulators”)


The subdirectory past-schedules contains the schedules (the .txt files) generated for previous calendars — i.e. by previous calls to club3 schedules. While optional, past-schedules is useful to maintain, as it provides the historical information necessary to ensure that no member is over- or underscheduled.

System requirements

club3 runs on any Common Lisp in a Unix-like environment, e.g. ABCL, CLISP, Clozure CL, CMUCL, ECL, and SBCL. (On Windows, you can get a Unix environment via Cygwin, which also provides CLISP.)