A very interesting question. I thought of a much plainer approach than the other responders but it proves to perform quite well. I simply PadRight
the reference sequence to match the length of the test sequence.
Update: limited extension to patterns within ref
and timings updated for version 10.1.0.
Functions
cycQ[ref_][test_] := test ~MatchQ~ PadRight[ref, Length @ test, ref]
cycpat[f_, r___] := p : PatternSequence[f, ___] /; cycQ[{f, r}][{p}] // Identity
cycQ
tests one sequence against another:
cycQ[{1, 2, 3}] /@ {{}, {1}, {1, 2, 3}, {1, 2, 3, 1}, {2, 3}}
{True, True, True, True, False}
cycpat
is the pattern-building function:
cycpat[1, 2, 3]
p$ : PatternSequence[1, ___] /; cycQ[{1, 2, 3}][{p$}]
Sample applications
Sample data:
SeedRandom[1]
test = RandomInteger[{1, 3}, 20]
{2, 1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 1, 3, 1, 2, 3, 1, 1, 2, 2}
Finding the single longest sequence in the list:
test /. {___, x : Longest @ cycpat[1, 2, 3], ___} :> {x}
{1, 2, 3, 1}
Finding all sequence fragments in a list, length 2 or greater:
ReplaceList[test, {___, x : cycpat[1, 2, 3] /; Length[{x}] > 1, ___} :> {x}]
{{1, 2}, {1, 2}, {1, 2}, {1, 2, 3}, {1, 2, 3, 1}, {1, 2}}
Performance
rm -rf's cyclicPatternMatchQ
, while certainly interesting, isn't fast enough to be widely applicable:
SeedRandom[1]
a = RandomInteger[{1, 5}, 300];
a /. {___, x : Longest@cycpat[1, 2, 3, 4, 5], ___} :> {x} // Timing
a /. {___, Longest@m__, ___} /;
cyclicPatternMatchQ[{1, 2, 3, 4, 5}][{m}] :> {m} // Timing
{0.145, {1, 2, 3, 4}}
{6.16204, {1, 2, 3, 4}}
belisarius's form
function is much faster but still not as fast as cycpat
:
form[w_List] := (* Note I removed the x and y patterns *)
PatternSequence[Longest@Repeated[PatternSequence @@ w, {0, Infinity}],
Alternatives @@
Table[Longest@Repeated[PatternSequence @@ w[[;; i]], {0, 1}], {i, Length[w], 1, -1}]]
SeedRandom[10]
big = RandomInteger[{1, 5}, 1200];
big /. {___, q : Longest @ cycpat[1, 2, 3, 4, 5], ___} :> {q} // Timing
big /. {___, q : Longest @ form @ {1, 2, 3, 4, 5}, ___} :> {q} // Timing
{6.18, {1, 2, 3, 4, 5, 1}}
{10.80, {1, 2, 3, 4, 5, 1}}
It is worth noting however that (use of) form
slows down semi-proportionately to the length of the sequence it is given, while cycpat
does not:
big /. {___, q : Longest[cycpat @@ Range[50]], ___} :> {q} // Timing
big /. {___, q : Longest @ form @ Range[50], ___} :> {q} // Timing
{6.282, {1, 2, 3, 4, 5}}
{63.586, {1, 2, 3, 4, 5}}
cycpat
still seems rather slow for a list of only 1200 elements but I was unable to improve its performance. Possibly a form of memoization would speed the highly repetitive application of cycQ
without unacceptable memory consumption.
Cases[{{q, r, a, b, a, b, a, s, e, f, a}}, {y___, x : Longest@PatternSequence[(PatternSequence[a, b] ...), a | PatternSequence[]], z___} :> {x}]
. I don't thinkPatternSequence
matches a sub-Sequence
by itself but needs to appear inside aHead
. I might be wrong, though. $\endgroup$___
alone will only match a single element, but{___}
will match a list with an arbitrary number of elements. It is only the complete list that will match, not it's elements separately. Example:ReplaceAll[Range[10], Longest[___Integer] -> x]
. This can't replace the whole sequence of numbers in one go. This can:ReplaceAll[Range[10], z_[___Integer] :> z[x]]
. $\endgroup$Cases
?Replace
?MatchQ
? Or a function definition? It might work best in the last case. $\endgroup$MatchQ
, but I would like it to be adaptable to bothCases
andReplace
. Usually, I'm working with lists, but I'd like this as general as possible. $\endgroup$