def lookupRecursive(self, node, time): '''Basic linked list node transversal''' if node == None: log('ERROR: No node, returning []') return [] if node.next == None: # Base case: next node is null, return this one return node.rule # Compare minutes and seconds (by making the hours equal) tempTime = Time(node.next.time.hours, time.minutes, time.seconds) if tempTime.toSeconds() < node.next.time.toSeconds(): # Base case: time comes before node.time, so return node.rule return node.rule return self.lookupRecursive(node.next, time)
def insert(self, node, rule, time, ruleChainHour): ''' Recursive functions can get really tricky really quickly sticky. Ideally, this docstring would contain some helpful hints. Here's some terminology: "this node" - the parameter called node "this rule" - the parameter called rule, not this node's rule Once upon a time, the code for this function was short and concise. Then the dawning of the "duration" attribute appeared. With this attribute, a constant and a rule of variants can overlap and overwrite other rules. The code is now much more complex, because each hour has to be properly partitioned by rules defined in all hours; and not only this, but this partitioned time has to be represented by linked lists and everything has to occur recursively. Keep in mind: Constants ALWAYS have a duration (if it isn't explicitly declared, then it defaults to the GCD). Rule with no duration are considered to be rules with INFINITE duration. Variant rules CAN have a duration; this acts to give them a lower priority in times past the end of the duration and a higher priority in times during the duration. The return value of this function is a RuleNode which (in the first level deep) becomes the new root node for that hour in the RuleChain. Therefore, it is helpful to think of the return value as a way to modify the parent node's next node. ''' # If true, rule will override node during conflicts precedence = ruleChainHour >= time.hours if node == None: # Base case: insert a new node into the rule chain by creating it # and setting its child to this node. return RuleNode(rule, time, node) # Assume the same hour as the node so comparisons will work tempTime = Time(node.time.hours, time.minutes, time.seconds) tempTime.duration = time.duration # At the highest level, we consider three cases: the time attached to # this rule is either before, equal to, or after the time of this node. if tempTime.toSeconds() < node.time.toSeconds(): # Time occurs before this node. In all cases, the rule is prepended # to the chain. Keep in mind, a time with no duration is basically # a time with infinite duration. Also keep in mind, Constants # ALWAYS have a duration. if not time.duration: return RuleNode(rule, time, node) # Three cases: rules don't overlap, rules overlap partially, rules overlap fully # Case 1: rules don't overlap if tempTime.end() <= node.time.toSeconds(): return RuleNode(rule, time, node) # Case 2: rules overlap partially if tempTime.end() < node.time.end(): if precedence: # Move node into the furture and shorten its duration newBeginning = Time.fromSeconds(tempTime.end()) newDuration = node.time.duration.toSeconds() - (node.time.end() - tempTime.end()) newBeginning.duration = Time.fromSeconds(newDuration) node.time = newBeginning return RuleNode(rule, time, node) else: # Shorten time time.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds()) return RuleNode(rule, time, node) # Case 3: node is fully overlapped by rule # time.end() >= node.time.end() if precedence: # Not including this node in the return statement effectively # eliminates it. However, things aren't this simple. We need to # check if the next node is partially/fully consumed via # recursion. return self.insert(node.next, rule, time, ruleChainHour) else: # Split the rule into two nodes that fall on either side of # this node. We create the following chain: # parent node -> node1 -> node -> node2 -> node.next time1 = time.copy() time1.duration = Time.fromSeconds(node.time.toSeconds() - tempTime.toSeconds()) node1 = RuleNode(rule, time1, node) time2 = Time.fromSeconds(node.time.end()) time2.hours = time.hours # Use original hours to maintain precedence time2.duration = Time.fromSeconds(tempTime.end() - node.time.end()) # Use recursion, because time2 might extend past node.next node2 = self.insert(node.next, rule, time2, ruleChainHour) node.next = node2 return node1 # The case where rule occured before node was relatively straightforward. # Now, rule and node occur at the same time, which means that most # likely either rule or node is omitted. if tempTime.toSeconds() == node.time.toSeconds(): if not precedence: # Ignore the rule return node # We've established that the rule has precedence. Now it's just a # matter of finding out how much of node (and its children) to # delete. # Three cases # Case 1: No rule duration if not tempTime.duration: # Replace the node return RuleNode(rule, time, node.next) # Case 2: Rule duration, but no node duration if not node.time.duration: # Peek ahead at the future node if node.next: tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds) tempTime2.duration = time.duration if tempTime2.end() > node.next.time.toSeconds(): # This node is fully engulfed. Replace the node, and # make sure that we replace any other overlapped nodes # (recursively, of course) # parent node -> node1 -> node.next node1 = self.insert(node.next, rule, time, ruleChainHour) return node1 # Make this node start at the end of this rule node.time = Time.fromSeconds(tempTime.end()) return RuleNode(rule, time, node) # Case 3: Rule duration AND node duration. Let the battle begin! if tempTime.end() >= node.time.end(): # Replace the node and any following nodes if necessary node1 = self.insert(node.next, rule, time, ruleChainHour) return node1 else: # Chop off and preserve the dangling part end = node.time.end() node.time = Time.fromSeconds(tempTime.end()) # node.time.hours is already set because tempTime.hours was copied earlier node.time.duration = Time.fromSeconds(end - tempTime.end()) return RuleNode(rule, time, node) # Rule occurs in the future: tempTime.toSeconds() > node.time.toSeconds() # Three cases: rules don't overlap, rules overlap partially, rules overlap fully # Case 1 if node.time.end() <= tempTime.toSeconds(): # If node.rule is a constant, it doesn't need to persist after its duration if not tempTime.duration or self.isConstant(node.rule): # Regular rule or node is constant, recurse deeper node.next = self.insert(node.next, rule, time, ruleChainHour) return node else: # Peek ahead at the future node if node.next: tempTime2 = Time(node.next.time.hours, time.minutes, time.seconds) tempTime2.duration = time.duration if tempTime2.toSeconds() > node.next.time.toSeconds(): # Next node is in the future too, recurse deeper node.next = self.insert(node.next, rule, time, ruleChainHour) return node # tempTime has a duration so node should persist after rule's completion # To do so, we dupe node and let recursion do the rest timeCopy = Time.fromSeconds(tempTime.toSeconds()) if node.time.duration: # Need to modify duration of both old and new time timeCopy.duration = Time.fromSeconds(node.time.end() - tempTime.toSeconds()) node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) nodeCopy = RuleNode(node.rule, timeCopy, node.next) node.next = self.insert(nodeCopy, rule, time, ruleChainHour) return node # Implicit that node.time.duration exists # Case 2 if tempTime.end() > node.time.end(): # Implicit that tempTime.duration exists if precedence: # Shorten node node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node else: # Shorten rule by giving it a later starting time time2 = Time.fromSeconds(node.time.end()) time2.hours = time.hours # Use original hours to maintain precedence time2.duration = Time.fromSeconds(tempTime.end() - node.time.end()) node.next = RuleNode(rule, time2, node.next) return node # Case 3: node.time has a duration and tempTime occurs in the middle of it if not precedence: if tempTime.duration: # tempTime is fully engulfed by node return node else: # tempTime is a rule, so it continues past node time2 = Time.fromSeconds(node.time.end()) node.next = self.insert(node.next, rule, time2, ruleChainHour) return node if not tempTime.duration: # tempTime is a regular rule, so chop node in half and occupy the # second half with this rule node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node if tempTime.end() == node.time.end(): # Ends coincide. Things are easy, just chop and link node.time = node.time.copy() node.time.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) # Link in the new node after this one node.next = RuleNode(rule, time, node.next) return node # Things are messy. Bisect node: # parent node -> node(1) -> rule -> node(2) -> node.next # Go from last to first. Start with second node: time2 = Time.fromSeconds(tempTime.end()) time2.duration = Time.fromSeconds(node.time.end() - tempTime.end()) node2 = RuleNode(node.rule, time2, node.next) newNode = RuleNode(rule, time, node2) time1 = node.time.copy() time1.duration = Time.fromSeconds(tempTime.toSeconds() - node.time.toSeconds()) node.time = time1 node.next = newNode return node