Coverage for src/seqrule/generators/lazy.py: 16%
43 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 10:39 -0600
« prev ^ index » next coverage.py v7.6.12, created at 2025-02-27 10:39 -0600
1"""
2Lazy sequence generation.
4This module provides functionality for lazy sequence generation,
5which only generates sequences as they are requested.
6"""
8import random
11class LazyGenerator:
12 """
13 Generator that lazily produces sequences.
15 This generator only creates sequences when they are requested, making
16 it more memory efficient for large domains or long sequences.
17 """
19 def __init__(self, domain, max_length=10, filter_rule=None):
20 """
21 Initialize with generation parameters.
23 Args:
24 domain: List of objects to generate sequences from
25 max_length: Maximum length of generated sequences
26 filter_rule: Optional rule to filter generated sequences
27 """
28 self.domain = domain
29 self.max_length = max_length
30 self.filter_rule = filter_rule
31 self._state = self._get_initial_state()
33 def _get_initial_state(self):
34 """Get the initial generator state."""
35 return {"generated": 0, "current_length": 0, "current_batch": []}
37 def __call__(self):
38 """Generate the next sequence."""
39 # Shortcut if we've already generated some for this length
40 if self._state["current_batch"]:
41 return self._state["current_batch"].pop()
43 # If we've reached the max length, start over
44 if self._state["current_length"] > self.max_length:
45 self._state = self._get_initial_state()
47 # Try to generate a sequence of the current length
48 length = self._state["current_length"]
50 # Empty sequence is a special case
51 if length == 0:
52 self._state["current_length"] = 1
53 if not self.filter_rule or self.filter_rule([]):
54 return []
55 else:
56 return self()
58 # Generate a batch of sequences for this length
59 batch_size = min(10, 100 // length) # Generate fewer longer sequences
60 batch = []
62 for _ in range(batch_size * 10): # Try harder for filtered sequences
63 if len(batch) >= batch_size:
64 break
66 sequence = random.choices(self.domain, k=length)
68 if not self.filter_rule or self.filter_rule(sequence):
69 batch.append(sequence)
71 # If we couldn't generate any, move to next length
72 if not batch:
73 self._state["current_length"] += 1
74 return self()
76 # Store the batch and return one
77 self._state["current_batch"] = batch[:-1]
78 self._state["generated"] += len(batch)
80 # If we've generated enough sequences of this length, move to next length
81 if self._state["generated"] >= 10 * (length + 1):
82 self._state["current_length"] += 1
83 self._state["generated"] = 0
85 return batch[-1]
87 def __iter__(self):
88 """Return an iterator that generates sequences."""
89 for length in range(self.max_length + 1):
90 # Yield a batch for each length
91 for _ in range(
92 min(100, 10**length)
93 ): # Limit number of sequences per length
94 yield self()
97def generate_lazy(domain, max_length=10, filter_rule=None):
98 """
99 Create a lazy sequence generator.
101 Args:
102 domain: List of objects to generate sequences from
103 max_length: Maximum length of generated sequences
104 filter_rule: Optional rule to filter generated sequences
106 Returns:
107 LazyGenerator instance
108 """
109 return LazyGenerator(domain, max_length, filter_rule)