"""iDRegEx — Algorithm 4 (arXiv 1004.2372).""" from .ikoa import ikoa from .rwrsq import rwr_sq from .grammar import ( Concat, Alt, Plus, Optional, Star, Symbol, Epsilon, Empty, alphabet, count_words, lang_size as grammar_lang_size, model_cost as grammar_model_cost, ) def is_deterministic(node): """Check if a k-ORE is deterministic (Glushkov determinism). A k-ORE is deterministic iff for every subexpression Alt([r, s, ...]), first(r) ∩ first(s) = ∅. """ if node is None or isinstance(node, (Empty, Epsilon)): return True return _check_det(node) def _check_det(node): """Recursive determinism check on AST nodes.""" if isinstance(node, Symbol): return True if isinstance(node, (Epsilon, Empty)): return True if isinstance(node, (Plus, Optional, Star)): return _check_det(node.child) if isinstance(node, Alt): first_sets = [_first_set(child) for child in node.parts] for j, fs1 in enumerate(first_sets): for fs2 in first_sets[j + 1:]: if fs1 & fs2: return False for child in node.parts: if not _check_det(child): return False return True if isinstance(node, Concat): for child in node.parts: if not _check_det(child): return False return True return True def _first_set(node): """Compute first(r) — set of alphabet symbols that can appear at the start of a word in L(r).""" if node is None or isinstance(node, Empty): return set() if isinstance(node, Epsilon): return set() if isinstance(node, Symbol): return {node.value} if isinstance(node, (Plus, Optional, Star)): return _first_set(node.child) if isinstance(node, Concat): if node.parts: return _first_set(node.parts[0]) return set() if isinstance(node, Alt): result = set() for child in node.parts: result |= _first_set(child) return result return set() def idregex(sequences, kmax=4, N=5, criterion='langsize'): """ |———— Algorithm 4: iDRegEx ————| Require: sample S Ensure: k-ORE r 1: C ← ∅ 2: for k = 1 to kmax do 3: for n = 1 to N do 4: G ← iKoa(S, k) 5: if rwr²(G) is deterministic then 6: add rwr²(G) to C 7: return best(C) """ C = set() for k in range(1, kmax + 1): for _ in range(N): G = ikoa(sequences, k, num_trials=1) if G is None: continue expr = rwr_sq(G) if expr is not None and not isinstance(expr, Empty): if is_deterministic(expr): C.add(expr) if not C: return None if criterion == 'langsize': return min(C, key=lambda e: (grammar_lang_size(e, 2 * grammar_model_cost(e) + 1), _ast_size(e))) return min(C, key=lambda e: _ast_size(e)) def _ast_size(node): """Count nodes in AST for length comparison.""" if isinstance(node, Symbol): return 1 if isinstance(node, (Epsilon, Empty)): return 1 if isinstance(node, (Plus, Optional, Star)): return 1 + _ast_size(node.child) if isinstance(node, (Concat, Alt)): return 1 + sum(_ast_size(child) for child in node.parts) return 1