A package-ready breadth-first position search, returning positions of a pattern in an expression. It allows top-down and bottom-up breadth-first traversals by setting level specification. It is not exactly the one Mr.Wizard was looking for, as it checks absolute levels rigorously (i.e. all level 4 subparts are checked before any level 3 subpart is visited). Deals with the usual level specifications and can return a limited number of cases if asked for.
Options[bfPosition] = {Heads -> True};
bfPosition[expr_, patt_, opts : OptionsPattern[]] :=
bfPosition[expr, patt, {0, ∞}, ∞, opts];
bfPosition[expr_, patt_, level_, opts : OptionsPattern[]] :=
bfPosition[expr, patt, level, ∞, opts];
bfPosition[expr_, patt_, level_, 0 | 0., opts : OptionsPattern[]] = {};
bfPosition[expr_, patt_, level_, n_, opts : OptionsPattern[]] /;
If[MatchQ[level, {_Integer | Infinity, _Integer | Infinity} |
{_Integer | Infinity} | _Integer | Infinity], True,
Message[bfPosition::level, level]; False] := Module[
{lev, max = Depth@expr, range, c = 0, found, reap},
(* Normalize level specification *)
lev = Switch[level /. Infinity -> max,
{_Integer, _Integer}, level,
{_Integer}, {First@level, First@level},
_Integer, {1, level}];
lev = (Min[#, max] & /@ (lev /. x_?Negative :> Max[(max + 1 + x), 0]));
range = Range[First@lev, Last@lev, If[Greater @@ lev, -1, 1]];
(* Check each level until the required amount of matches are found *)
reap = Last@Reap@Do[
found = Position[expr, patt, {i, i}, n - c, Heads -> OptionValue@Heads];
c = c + Length@found;
Sow@found;
If[c >= n, Return[]];,
{i, range}];
If[reap === {}, {}, Join @@ (First@reap)]
];
bfPosition[expr, pattern]
gives a list of the positions at which objects matching pattern
appear in expr
by performing a breadth-first search of subparts. Position[expr, pattern, levelspec]
finds only objects that appear on levels specified by levelspec
. Position[expr, pattern, levelspec, n]
gives the positions of the first n
objects found. bfPosition
effectively accepts reverse-ordered level specifications that define the order of search in expr: for example bfPosition[expr, pattern, {∞, 0}]
performs a bottom-up while bfPosition[expr, pattern, {0, ∞}]
performs a top-down breadth-first search.
Test it:
expr = {{1, {2, 3}}, {4, 5}};
pos = bfPosition[expr, _, {∞, 0}, Heads -> False];
If[# === {}, expr, Extract[expr, #]] & /@ pos
{2, 3, 1, {2, 3}, 4, 5, {1, {2, 3}}, {4, 5}, {{1, {2, 3}}, {4, 5}}}
Note that all level-3 objects (2
, 3
) are visited before encountering a level-2 leaf (1
).
bfPosition
is not like Position
(Position
does a depth-first postorder search):
bfPosition[expr, _, Heads -> False]
Position[expr, _, Heads -> False]
{{}, {1}, {2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}, {1, 2, 1}, {1, 2, 2}}
{{1, 1}, {1, 2, 1}, {1, 2, 2}, {1, 2}, {1}, {2, 1}, {2, 2}, {2}, {}}
Find positions using bottom-up or top-down search:
bfPosition[expr, _, {∞, 0}, Heads -> False]
bfPosition[expr, _, {0, ∞}, Heads -> False]
{{1, 2, 1}, {1, 2, 2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}, {1}, {2}, {}}
{{}, {1}, {2}, {1, 1}, {1, 2}, {2, 1}, {2, 2}, {1, 2, 1}, {1, 2, 2}}
Find a limited number of occurrences only:
bfPosition[expr, _, {∞, 0}, 4, Heads -> False]
bfPosition[expr, _, {0, ∞}, 4, Heads -> False]
{{1, 2, 1}, {1, 2, 2}, {1, 1}, {1, 2}}
{{}, {1}, {2}, {1, 1}}
Map
andMapAll
perform a df traversal? $\endgroup$BreadthFirstScan
. $\endgroup$