@@ -69,15 +69,8 @@ class JSONPathRecursiveDescentSegment(JSONPathSegment):
6969
7070 def resolve (self , nodes : Iterable [JSONPathNode ]) -> Iterable [JSONPathNode ]:
7171 """Select descendants of each node in _nodes_."""
72- # The nondeterministic visitor never generates a pre order traversal, so we
73- # still use the deterministic visitor 20% of the time, to cover all
74- # permutations.
75- #
76- # XXX: This feels like a bit of a hack.
7772 visitor = (
78- self ._nondeterministic_visit
79- if self .env .nondeterministic and random .random () < 0.8 # noqa: S311, PLR2004
80- else self ._visit
73+ self ._nondeterministic_visit if self .env .nondeterministic else self ._visit
8174 )
8275
8376 for node in nodes :
@@ -114,51 +107,51 @@ def _visit(self, node: JSONPathNode, depth: int = 1) -> Iterable[JSONPathNode]:
114107 def _nondeterministic_visit (
115108 self ,
116109 root : JSONPathNode ,
117- _ : int = 1 ,
110+ depth : int = 1 ,
118111 ) -> Iterable [JSONPathNode ]:
119- def _children (node : JSONPathNode ) -> Iterable [JSONPathNode ]:
120- if isinstance (node .value , dict ):
121- items = list (node .value .items ())
122- random .shuffle (items )
123- for name , val in items :
124- if isinstance (val , (dict , list )):
125- yield JSONPathNode (
126- value = val ,
127- location = node .location + (name ,),
128- root = node .root ,
129- )
130- elif isinstance (node .value , list ):
131- for i , element in enumerate (node .value ):
132- if isinstance (element , (dict , list )):
133- yield JSONPathNode (
134- value = element ,
135- location = node .location + (i ,),
136- root = node .root ,
137- )
138-
112+ """Nondeterministic node traversal."""
139113 # (node, depth) tuples
140114 queue : Deque [Tuple [JSONPathNode , int ]] = deque ()
141115
142- yield root # visit the root node
143- queue .extend ([(child , 1 ) for child in _children (root )]) # queue root's children
116+ # Visit the root node
117+ yield root
118+
119+ # Queue root's children
120+ queue .extend ([(child , depth ) for child in _nondeterministic_children (root )])
144121
145122 while queue :
146- _node , depth = queue .popleft ()
123+ node , depth = queue .popleft ()
124+ yield node
147125
148126 if depth >= self .env .max_recursion_depth :
149127 raise JSONPathRecursionError (
150128 "recursion limit exceeded" , token = self .token
151129 )
152130
153- yield _node
154-
155- # Visit child nodes now or queue them for later?
131+ # Randomly choose to visit child nodes now or queue them for later?
156132 visit_children = random .choice ([True , False ]) # noqa: S311
157133
158- for child in _children ( _node ):
134+ for child in _nondeterministic_children ( node ):
159135 if visit_children :
160136 yield child
161- queue .extend ([(child , depth + 2 ) for child in _children (child )])
137+
138+ # Queue grandchildren by randomly interleaving them into the
139+ # queue while maintaining queue and grandchild order.
140+ grandchildren = [
141+ (child , depth + 2 )
142+ for child in _nondeterministic_children (child )
143+ ]
144+
145+ queue = deque (
146+ [
147+ next (n )
148+ for n in random .sample (
149+ [iter (queue )] * len (queue )
150+ + [iter (grandchildren )] * len (grandchildren ),
151+ len (queue ) + len (grandchildren ),
152+ )
153+ ]
154+ )
162155 else :
163156 queue .append ((child , depth + 1 ))
164157
@@ -174,3 +167,23 @@ def __eq__(self, __value: object) -> bool:
174167
175168 def __hash__ (self ) -> int :
176169 return hash ((".." , self .selectors , self .token ))
170+
171+
172+ def _nondeterministic_children (node : JSONPathNode ) -> Iterable [JSONPathNode ]:
173+ """Yield children of _node_ with nondeterministic object/dict iteration."""
174+ if isinstance (node .value , dict ):
175+ items = list (node .value .items ())
176+ random .shuffle (items )
177+ for name , val in items :
178+ yield JSONPathNode (
179+ value = val ,
180+ location = node .location + (name ,),
181+ root = node .root ,
182+ )
183+ elif isinstance (node .value , list ):
184+ for i , element in enumerate (node .value ):
185+ yield JSONPathNode (
186+ value = element ,
187+ location = node .location + (i ,),
188+ root = node .root ,
189+ )
0 commit comments