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

1""" 

2Lazy sequence generation. 

3 

4This module provides functionality for lazy sequence generation, 

5which only generates sequences as they are requested. 

6""" 

7 

8import random 

9 

10 

11class LazyGenerator: 

12 """ 

13 Generator that lazily produces sequences. 

14 

15 This generator only creates sequences when they are requested, making 

16 it more memory efficient for large domains or long sequences. 

17 """ 

18 

19 def __init__(self, domain, max_length=10, filter_rule=None): 

20 """ 

21 Initialize with generation parameters. 

22 

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() 

32 

33 def _get_initial_state(self): 

34 """Get the initial generator state.""" 

35 return {"generated": 0, "current_length": 0, "current_batch": []} 

36 

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() 

42 

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() 

46 

47 # Try to generate a sequence of the current length 

48 length = self._state["current_length"] 

49 

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() 

57 

58 # Generate a batch of sequences for this length 

59 batch_size = min(10, 100 // length) # Generate fewer longer sequences 

60 batch = [] 

61 

62 for _ in range(batch_size * 10): # Try harder for filtered sequences 

63 if len(batch) >= batch_size: 

64 break 

65 

66 sequence = random.choices(self.domain, k=length) 

67 

68 if not self.filter_rule or self.filter_rule(sequence): 

69 batch.append(sequence) 

70 

71 # If we couldn't generate any, move to next length 

72 if not batch: 

73 self._state["current_length"] += 1 

74 return self() 

75 

76 # Store the batch and return one 

77 self._state["current_batch"] = batch[:-1] 

78 self._state["generated"] += len(batch) 

79 

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 

84 

85 return batch[-1] 

86 

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() 

95 

96 

97def generate_lazy(domain, max_length=10, filter_rule=None): 

98 """ 

99 Create a lazy sequence generator. 

100 

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 

105 

106 Returns: 

107 LazyGenerator instance 

108 """ 

109 return LazyGenerator(domain, max_length, filter_rule)