| Total Complexity | 48 |
| Total Lines | 175 |
| Duplicated Lines | 29.14 % |
| Changes | 0 | ||
Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like water_jars often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.
Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.
| 1 | gFirst = 0 |
||
| 2 | gSecond = 0 |
||
| 3 | |||
| 4 | |||
| 5 | class AStar(object): |
||
| 6 | def __init__(self, Goal): |
||
| 7 | self.Goal = Goal |
||
| 8 | |||
| 9 | def Heuristic(self, Node): |
||
| 10 | raise NotImplementedError |
||
| 11 | |||
| 12 | def GetResult(self, Node): |
||
| 13 | raise NotImplementedError |
||
| 14 | |||
| 15 | def Search(self, StartNode): |
||
| 16 | OpenSet = set() |
||
| 17 | ClosedSet = set() |
||
| 18 | StartNode.H = self.Heuristic(StartNode) |
||
| 19 | OpenSet.add(StartNode) |
||
| 20 | while OpenSet: |
||
| 21 | Current = min(OpenSet, key=lambda o: o.G + o.H) |
||
| 22 | if self.Goal(Current.Status): |
||
| 23 | return self.GetResult(Current) |
||
| 24 | OpenSet.remove(Current) |
||
| 25 | ClosedSet.add(Current) |
||
| 26 | for Node in Current.PossibleNextNodes(): |
||
| 27 | if Node.Status in [i.Status for i in ClosedSet]: |
||
| 28 | continue |
||
| 29 | if Node.Status in [i.Status for i in OpenSet]: |
||
| 30 | index = [i.Status for i in OpenSet].index(Node.Status) |
||
| 31 | if Node.G < list(OpenSet)[index].G: |
||
| 32 | list(OpenSet)[index].G = Node.G |
||
| 33 | list(OpenSet)[index].Parent = Node.Parent |
||
| 34 | else: |
||
| 35 | Node.H = self.Heuristic(Node) |
||
| 36 | if Node.SelfCheck: |
||
| 37 | OpenSet.add(Node) |
||
| 38 | return None |
||
| 39 | |||
| 40 | |||
| 41 | class AStarNode(object): |
||
| 42 | def __init__(self, Status, G, Parent): |
||
| 43 | self.G = G |
||
| 44 | self.H = None |
||
| 45 | self.Parent = Parent |
||
| 46 | self.Status = Status |
||
| 47 | self.Comment = None |
||
| 48 | |||
| 49 | def SelfCheck(self): |
||
| 50 | if self.G and self.H and self.Status: |
||
| 51 | return True |
||
| 52 | else: |
||
| 53 | print(self.G) |
||
| 54 | print(self.H) |
||
| 55 | print(self.Parent) |
||
| 56 | print(self.Status) |
||
| 57 | print(self.Comment) |
||
| 58 | assert 1 == 0 |
||
| 59 | |||
| 60 | def PossibleNextNodes(self): |
||
| 61 | raise NotImplementedError |
||
| 62 | |||
| 63 | |||
| 64 | class WaterJarPuzzle(AStar): |
||
| 65 | def __init__(self, Goal): |
||
| 66 | super(WaterJarPuzzle, self).__init__(Goal) |
||
| 67 | |||
| 68 | def Heuristic(self, Node): |
||
| 69 | return 0 |
||
| 70 | |||
| 71 | def GetResult(self, Node): |
||
| 72 | Result = [] |
||
| 73 | while Node.Parent: |
||
| 74 | Result.insert(0, Node.Comment) |
||
| 75 | Node = Node.Parent |
||
| 76 | return Result |
||
| 77 | |||
| 78 | |||
| 79 | class WaterJarPuzzleNode(AStarNode): |
||
| 80 | def __init__(self, Status, G, Parent): |
||
| 81 | super(WaterJarPuzzleNode, self).__init__(Status, G, Parent) |
||
| 82 | |||
| 83 | def PossibleNextNodes(self): |
||
| 84 | result = [] |
||
| 85 | # empty first |
||
| 86 | NewNode = WaterJarPuzzleNode((0, self.Status[1]), self.G + 1, self) |
||
| 87 | NewNode.Comment = '10' |
||
| 88 | result.append(NewNode) |
||
| 89 | # empty second |
||
| 90 | NewNode = WaterJarPuzzleNode((self.Status[0], 0), self.G + 1, self) |
||
| 91 | NewNode.Comment = '20' |
||
| 92 | result.append(NewNode) |
||
| 93 | # fill first jar |
||
| 94 | if self.Status[0] < gFirst: |
||
| 95 | NewNode = WaterJarPuzzleNode((gFirst, self.Status[1]), self.G + 1, self) |
||
| 96 | NewNode.Comment = '01' |
||
| 97 | result.append(NewNode) |
||
| 98 | # fill second jar |
||
| 99 | if self.Status[1] < gSecond: |
||
| 100 | NewNode = WaterJarPuzzleNode((self.Status[0], gSecond), self.G + 1, self) |
||
| 101 | NewNode.Comment = '02' |
||
| 102 | result.append(NewNode) |
||
| 103 | # first -> second |
||
| 104 | View Code Duplication | if self.Status[1] < gSecond: |
|
|
|
|||
| 105 | if (gSecond - self.Status[1]) >= self.Status[0]: |
||
| 106 | NewNode = WaterJarPuzzleNode((0, sum(self.Status)), self.G + 1, self) |
||
| 107 | else: |
||
| 108 | NewNode = WaterJarPuzzleNode( |
||
| 109 | (self.Status[0] - (gSecond - self.Status[1]), gSecond), |
||
| 110 | self.G + 1, |
||
| 111 | self, |
||
| 112 | ) |
||
| 113 | NewNode.Comment = '12' |
||
| 114 | result.append(NewNode) |
||
| 115 | # second -> first |
||
| 116 | View Code Duplication | if self.Status[0] < gFirst: |
|
| 117 | if (gFirst - self.Status[0]) >= self.Status[1]: |
||
| 118 | NewNode = WaterJarPuzzleNode((sum(self.Status), 0), self.G + 1, self) |
||
| 119 | else: |
||
| 120 | NewNode = WaterJarPuzzleNode( |
||
| 121 | (gFirst, self.Status[1] - (gFirst - self.Status[0])), |
||
| 122 | self.G + 1, |
||
| 123 | self, |
||
| 124 | ) |
||
| 125 | NewNode.Comment = '21' |
||
| 126 | result.append(NewNode) |
||
| 127 | return result |
||
| 128 | |||
| 129 | |||
| 130 | def checkio(first, second, goal): |
||
| 131 | global gFirst, gSecond |
||
| 132 | gFirst = first |
||
| 133 | gSecond = second |
||
| 134 | Puzzle = WaterJarPuzzle(lambda x, g=goal: g in x) |
||
| 135 | startNode = WaterJarPuzzleNode((0, 0), 0, None) |
||
| 136 | return Puzzle.Search(startNode) |
||
| 137 | |||
| 138 | |||
| 139 | if __name__ == '__main__': |
||
| 140 | # This part is using only for self-checking and not necessary for |
||
| 141 | # auto-testing |
||
| 142 | View Code Duplication | def check_solution(func, initial_data, max_steps): |
|
| 143 | first_volume, second_volume, goal = initial_data |
||
| 144 | actions = { |
||
| 145 | "01": lambda f, s: (first_volume, s), |
||
| 146 | "02": lambda f, s: (f, second_volume), |
||
| 147 | "12": lambda f, s: ( |
||
| 148 | f - (second_volume - s if f > second_volume - s else f), |
||
| 149 | second_volume if f > second_volume - s else s + f, |
||
| 150 | ), |
||
| 151 | "21": lambda f, s: ( |
||
| 152 | first_volume if s > first_volume - f else s + f, |
||
| 153 | s - (first_volume - f if s > first_volume - f else s), |
||
| 154 | ), |
||
| 155 | "10": lambda f, s: (0, s), |
||
| 156 | "20": lambda f, s: (f, 0), |
||
| 157 | } |
||
| 158 | first, second = 0, 0 |
||
| 159 | result = func(*initial_data) |
||
| 160 | if len(result) > max_steps: |
||
| 161 | print("You answer contains too many steps. It can be shorter.") |
||
| 162 | return False |
||
| 163 | for act in result: |
||
| 164 | if act not in actions.keys(): |
||
| 165 | print("I don't know this action {0}".format(act)) |
||
| 166 | return False |
||
| 167 | first, second = actions[act](first, second) |
||
| 168 | if goal == first or goal == second: |
||
| 169 | return True |
||
| 170 | print("You did not reach the goal.") |
||
| 171 | return False |
||
| 172 | |||
| 173 | assert check_solution(checkio, (5, 7, 6), 10), "Example" |
||
| 174 | assert check_solution(checkio, (3, 4, 1), 2), "One and two" |
||
| 175 |