Roan provides a simple pattern language for matching rows. This is useful, among other things, for counting rows considered particularly musical or unmusical.
A pattern string describes the bells in a row, with several kinds of wildcards and other constructs matching multiple bells. Bells’ names match themselves, so, for example, "13572468" matches queens on eight. A question mark matches any bell, and an asterisk matches runs of zero or more bells. Thus "*7468", at major, matches all twenty-four 7468s, and "?5?6?7?8" matches all twenty-four major rows that have the 5-6-7-8 in the positions they are in in tittums. Alternatives can be separated by the pipe character, ‘|’. Thus "13572468|12753468" matches either queens or Whittingtons. Concatentation of characters binds more tightly than alternation, but parentheses can be used to group subexpressions. Thus "*(4|5|6)(4|5|6)78" at major matches all 144 combination rollups. When matched against two major rows "?*12345678*?" matches wraps of rounds, but not either row being rounds.
Two further notations are possible. In each case it does not extend what can be expressed, it merely makes more compact something that can be expressed with the symbols already described. The first is a bell class, which consits of one or more bell names within square brackets, and indicates any one of those bells. Thus an alternative way to match the 144 combination rollups at major is "*[456][456]78".
A more compact notation is also available for describing runs of consecutive bells. Two bell symbols separated by a hyphen represent the run of bells from one to the other. Thus "*5-T" matches all rows ending 567890ET. If such a run description is followed by a solidus, ‘/’, and a one or two digit integer, it matches all runs of the length of that integer that are subsequences of the given run. Thus "*2-8/4" is equivalent to "*(2345|3456|4567|5678)". If instead of a solidus a percent sign, ’%’, is used it matches subsequences of both the run and its reverse. Thus "1-6%4*" matches all little bell runs off the front of length four selected from the bells 1 through 6, and is equivalent to the pattern "(1234|4321|2345|5432|3456|6543)*". There is some possible ambiguity with this notation, in that the second digit of an integer following a solidus or percent sign could be interpreted as a digit or a bell symbol. In these cases it is always interpreted as a digit, but the other use can be specified by using parentheses or a space.
Spaces, but no other whitespace, can be included in patterns. However no spaces may be included within bell classes or run descriptions. Thus " 123 [456] 7-T/3 * " is equivalent to "123[456]7-T/3*", but both "123[ 4 5 6 ]7-T/3*" and "123[456]7-T / 3*" are illegal, and will cause an error to be signaled.
In addition to strings, patterns may be represented by parse trees, which are simple list
structures made up of keywords and bells (that is, small, non-negative integers). Strings
are generally more convenient for reading and writing patterns by humans, but parse trees
can be more convenient for programmatically generated patterns. The function
pattern-parse
converts the string representation of a pattern to such a tree
structure. Sequences of elements are represented by lists starting with :sequence
;
alternatives by lists starting with :or
; bell classes by lists of the included
bells preceded by :class
; runs by a list of the form (:run start
end length bi)
, where start is the starting bell
,
end the ending bell
, length the length of the run, and bi is a
generalized boolean saying whether or not the runs are bidirectional; bell
s are
represented by themselves; and ‘?’ and ‘*’ by :one
and :any
,
respectively. The elements of the :sequence
and :or
lists may also be lists
themselves, representating subexpressions. For example, the string "(?[234]*|*4-9%4?)*T"
is equivalent to the tree
(:sequence (:or (:sequence :one (:class 1 2 3) :any) (:sequence :any (:run 3 8 4 t) :one)) :any 11)
Determines whether row, or pair of consecutive row
s, row and
following-row, match a pattern. If following-row is supplied it should be of
the same stage as row. The pattern may be a string or a tree, and should be
constructed to be appropriate for the stage of row; an error is signaled if it
contains explicit matches for bells of higher stage than row. Returns a generalized
boolean indicating whether or not pattern matches.
(row-match-p "*[456][456]78" !32516478) ⇒ t (row-match-p "*[456][456]78" !12453678) ⇒ nil (row-match-p "*[456][456]78" !9012345678) ⇒ t (row-match-p "?*123456*?" !651234 !562143) ⇒ t (row-match-p "?*123456*?" !651234 !652143) ⇒ nil (row-match-p "?*123456*?" !123456) ⇒ nil (row-match-p '(:sequence :any 6 7) !65432178) ⇒ t (row-match-p '(:sequence :any 6 7) !23456781) ⇒ nil
Signals an error if pattern cannot be parsed as a pattern, if row is not a
row
, if following-row is neither a row
nor nil
, if
pattern contains bells above the stage of row, or if following-row is a
row
of a different stage than row.
Care should be used when matching against two rows. In the usual use case when searching for things like wraps every row typically will be passed twice to this method, first as row and then as following-row. A naive pattern might end up matching twice, and thus double counting. For example, if at major "*12345678*" were used to search for wraps of rounds it would match whenever row or following-row were themselves rounds, possibly leading to double counting. Instead a search for wraps of rounds might be better done against something like "?*12345678*?".
Converts a string representation of a pattern to its parse tree, and returns it. The
stage is the stage for which pattern is parsed, and defaults to
*default-stage*
. If pattern is a non-empty list it is presumed to be a
pattern parse tree and is returned unchanged. Signals a type-error
if pattern
is neither a string nor a non-empty list, or if stage is not a stage
. Signals
a parse-error
if pattern is a string but cannot be parsed as a pattern, or
contains bells above those appropriate for stage.
(parse-pattern "(?[234]*|*4-9%4?)*T" 12) ⇒ (:sequence (:or (:sequence :one (:class 1 2 3) :any) (:sequence :any (:run 3 8 4 t) :one)) :any 11)
Returns a string that if parsed with parse-pattern
, would return the parse tree
tree. Note that the generation of a suitable string from tree is not unique,
and this function simply returns one of potentially many equivalent possibilities. The
case of any bells represented by letters is controlled by upper-case, which defaults
to the current value of *print-bells-upper-case*
. Signals an error if tree is not
a parse tree for a pattern.
(format-pattern '(:sequence 0 1 2 :any 7) t) ⇒ "123*8"
Returns a pattern, as a parse tree, that matches a named row at
stage. The name is one of those listed below. If stage is not supplied
it defaults to the current value of *default-stage*
. If covered, a
generalized boolean, is non-nil the row
(’s) that will be matched will assume an
implicit tenor. If covered is not supplied it defaults to nil
for even stages
and t
for odd stages. If there is no such named row known that corresponds to the
values of stage and covered nil
is returned. Signals an error if
name is not a keyword or is not a known named row name as enumerated below, or if
stage is not a stage
.
The supported values for name, and the stages at which they are defined, are:
:backrounds
any stage
:queens
uncovered singles and above, or covered two and above.
:kings
uncovered minimus and above, or covered singles and above; note that kings at uncovered minor or covered doubles is the same row as Whittingtons at those stages
:whittingtons
uncovered minor and above, or covered doubles and above; note that Whittingtons at uncovered minor or covered doubles is the same row as kings at those stages
:double-whittingtons
covered cinques or uncovered maximus, only
:roller-coaster
covered caters or uncovered royal, only
:near-miss
any stage
(format-pattern (named-row-pattern :whittingtons 10 nil)) ⇒ "1234975680" (format-pattern (named-row-pattern :whittingtons 9 t) ⇒ "123497568" (format-pattern (named-row-pattern :whittingtons 9 nil)) ⇒ "123864579" (named-row-pattern :whittingtons 4) ⇒ nil
An error signaled when attempting to parse a malformed row pattern.
Contains three potenitally useful slots accessible with pattern-parse-error-message
,
pattern-parse-error-pattern
and pattern-parse-error-index
.
• Counting matches |
Often one would like to count how many times a variety of patterns match many different
rows. To support this use Roan provides match-counter
s. After creating a
match-counter
with make-match-counter
you add a variety of patterns to it,
with add-pattern
or add-patterns
, each with a label, which will typically
be a symbol or string, but can be any Lisp object. You then apply the match-counter
to row
s with record-matches
, and query how many matches have occurred with
match-counter-counts
.
The order in which patterns are added to a match-counter
is preserved, and is
reflected in the return values of match-counter-labels
, and
match-counter-counts
called without a second argument. Replacing an existing
pattern by adding a different one with a label that is equalp
to an existing
one does not change the order, but deleting a pattern with remove-pattern
and
then re-adding it does move it to the end of the order. When a pattern has been replaced
by one with an equalp
label that is not eq
to the original label
which label is retained is undefined.
A match-counter
also distinguishes matches that occur at handstroke from those
that occur at backstroke. Typically you tell the match-counter
which stroke the
next row
it is asked to match is on, and it then automatically alternates
handstrokes and backstrokes for subsequent row
s. For patterns that span two
rows, such as wraps, the stroke is considered to be that between the rows; for example a
wrap of rounds that spans a backstroke lead would be considered to be “at” backstroke.
(let ((m (make-match-counter 8))) (add-patterns m '((cru "*[456][456]78") (wrap "?*12345678*?" t) (lb4 "1-7%4*|*1-7%4"))) (loop for (row following) on (generate-rows #8!36.6.5.3x5.56.5,2) do (record-matches m row following)) (values (match-counter-counts m))) ⇒ ((cru . 3) (wrap . 1) (lb4 . 5))
Used to collect statistics on how many rows match a variety of patterns.
Returns a fresh match-counter
, initially containing no patterns, that is
configured to attempt to match patterns against row
s of stage bells.
If not supplied, stage defaults to the current value of *default-stage*
.
Attempts to add patterns only appropriate for a different stage or match rows of a
different stage with record-matches
will signal an error.
Adds one or more patterns to those matched by the match-counter
count.
A single pattern, pattern, is added, with label label, by add-pattern
.
If the generalized boolean double-row-p is true two rows (which typically should be
consecutive) will be matched against pattern, and others one row; if not supplied
double-row-p is nil
. Multiple patterns may be added together with
add-patterns
: lists should be a list of lists, where the sublists are of the
form (label pattern &optional double-row-p)
, and the patterns are
added in the order given. In either case the pattern may be either a string or list
structure that is a parsed pattern, such as returned by parse-pattern
. If
label is equalp
to the label of a pattern already added to counter that
pattern will be replaced, and its corresponding counts reset to zero. Either function
reeturns counter. Either signals a type-error
if counter is not a
match-counter
. Signals an error if any of the patterns are not an
appropriate pattern for the stage of counter.
Removes any pattern in method-counter
count with its label equalp
to label. Returns t
if such a pattern was found and removed, and nil
otherwise. Signals a type-error
if count is not a method-counter
.
Removes all the patterns in the method-counter
counter, and returns a
positive integer, the number of patterns so removed, if any, or nil
if counter
had no patterns. Signals a type-error
if counter is not a
match-counter
.
Returns two values: the first is the pattern whose label in count is
equalp
to label, if any, and otherwise nil
; the second is a
generalized boolean if and only if the first value is non-nil and the pattern is to be
matched against two rows rather than just one. If the generalized boolean as-string
is true the pattern is returned as a string, as by format-pattern
, with the case of
any bells represented by letters controled by the generalized boolean upper-case;
and otherwise as a parse tree, as by parse-pattern
. A string return value may not
be string-equal
to that added to counter, but will match the same
row
s. If as-string is not supplied it defaults to true; if upper-case
is not supplied it defaults to the current value of *print-bells-upper-case*
.
Signals a type-error
if counter is not a match-counter
.
Returns two lists, the labels of those patterns in count that are matched against
a single row, and those that are matched against two rows. Both lists are in the order in
which the corresponding patterns were first added to counter. Signals a
type-error
if counter is not a match-counter
.
Returns three values, the number of times the pattern with label equalp
to
label in counter has matched row
s presented to it with
record-matches
since counter was reset or the relevent pattern was added to
it. The first return value is the total number of matches, the second the number of
matches at handstroke, and the third the number of matches at backstroke. If no
label is supplied it instead returns three a-lists mapping the labels of the
patterns in counter to the number of matches, again total, handstroke and
backstroke. The elements of these a-lists are in the order in which the corresponding
patterns were first added to counter. Returns nil
if there is no pattern
labeled label. Signals a type-error
if counter is not a
match-counter
.
Resets all the counts associated with all the patterns in counter to zero.
Signals a type-error
if counter is not a match-counter
.
Returns a generalized boolean indicating that the next row presented to counter
will be a handstroke. Can be used with setf
to tell counter whether or not it
should consider the next row a handstroke or a backstroke. If not explicitly set again,
either with (setf match-counter-handstroke-p)
, or with the handstroke-p
argument to record-matches
, whether or not subsequent rows will be considered
handstroke or backstroke will alternate. Signals a type-error
if counter is
not a match-counter
.
Causes all the single-row patterns of counter to be matched against row,
and, if a following-row is supplied and not nil
, also all the double-row
patterns to be matched against both rows. If the generalized boolean handstroke-p is
supplied it indicates whether row is to be considered a handstroke or not, and,
unless explicitly set again, either with the handstroke-p argument to
record-matches
by with (setf match-counter-handstroke-p)
, whether or not
subsequent rows will be considered handroke or backstroke will alternate. That is,
supplying a handtroke-p argument to record-matches
is equivalent to calling
(setf match-counter-handstoke-p)
immediately before it. Signals a type-error
if counter is not a match-counter
, row is not a row
, or
following-row is neither a row
nor nil
.