| Total Complexity | 42 |
| Total Lines | 147 |
| Duplicated Lines | 18.37 % |
| 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 express_delivery 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 | import heapq |
||
| 2 | from collections import defaultdict |
||
| 3 | from itertools import permutations |
||
| 4 | |||
| 5 | |||
| 6 | def translate_route(path): |
||
| 7 | result = [] |
||
| 8 | for i in zip(path, path[1:]): |
||
| 9 | if i[0][0] < i[1][0]: |
||
| 10 | result.append('D') |
||
| 11 | elif i[0][0] > i[1][0]: |
||
| 12 | result.append('U') |
||
| 13 | elif i[0][1] < i[1][1]: |
||
| 14 | result.append('R') |
||
| 15 | else: |
||
| 16 | result.append('L') |
||
| 17 | return ''.join(result) |
||
| 18 | |||
| 19 | |||
| 20 | View Code Duplication | def shortest_path(graph, start, end): |
|
|
|
|||
| 21 | queue = [(0, start, [])] |
||
| 22 | seen = set() |
||
| 23 | while True: |
||
| 24 | (cost, v, path) = heapq.heappop(queue) |
||
| 25 | if v not in seen: |
||
| 26 | path = path + [v] |
||
| 27 | seen.add(v) |
||
| 28 | if v == end: |
||
| 29 | break |
||
| 30 | for (next_item, c) in graph[v].items(): |
||
| 31 | heapq.heappush(queue, (cost + c, next_item, path)) |
||
| 32 | return translate_route(path) |
||
| 33 | |||
| 34 | |||
| 35 | def build_topology(field_map): |
||
| 36 | result = defaultdict(dict) |
||
| 37 | cols = len(field_map[0]) |
||
| 38 | rows = len(field_map) |
||
| 39 | |||
| 40 | for y in range(rows - 1): |
||
| 41 | for x in range(cols - 1): |
||
| 42 | if field_map[y][x] != 'W': |
||
| 43 | if field_map[y + 1][x] != 'W': |
||
| 44 | result[(y, x)][(y + 1, x)] = 1 |
||
| 45 | result[(y + 1, x)][(y, x)] = 1 |
||
| 46 | if field_map[y][x + 1] != 'W': |
||
| 47 | result[(y, x)][(y, x + 1)] = 1 |
||
| 48 | result[(y, x + 1)][(y, x)] = 1 |
||
| 49 | # last col |
||
| 50 | for y in range(rows - 1): |
||
| 51 | if field_map[y][cols - 1] != 'W': |
||
| 52 | if field_map[y + 1][cols - 1] != 'W': |
||
| 53 | result[(y, cols - 1)][(y + 1, cols - 1)] = 1 |
||
| 54 | result[(y + 1, cols - 1)][(y, cols - 1)] = 1 |
||
| 55 | # last row |
||
| 56 | for x in range(cols - 1): |
||
| 57 | if field_map[rows - 1][x] != 'W': |
||
| 58 | if field_map[rows - 1][x] != 'W': |
||
| 59 | result[(rows - 1, x)][(rows - 1, x + 1)] = 1 |
||
| 60 | result[(rows - 1, x + 1)][(rows - 1, x)] = 1 |
||
| 61 | return result |
||
| 62 | |||
| 63 | |||
| 64 | def find_spots(target, field_map): |
||
| 65 | target_index = [i for i, v in enumerate(''.join(field_map)) if v == target] |
||
| 66 | return [(i // len(field_map[0]), i % len(field_map[0])) for i in target_index] |
||
| 67 | |||
| 68 | |||
| 69 | def checkio(field_map): |
||
| 70 | start_point = find_spots('S', field_map)[0] |
||
| 71 | end_point = find_spots('E', field_map)[0] |
||
| 72 | boxes = find_spots('B', field_map) |
||
| 73 | graph = build_topology(field_map) |
||
| 74 | |||
| 75 | direct_route = shortest_path(graph, start_point, end_point) |
||
| 76 | unpack_routes = {} |
||
| 77 | for i in permutations(boxes, 2): |
||
| 78 | unpack_routes[i] = [ |
||
| 79 | shortest_path(graph, start_point, i[0]), |
||
| 80 | 'B' + shortest_path(graph, i[0], i[1]) + 'B', |
||
| 81 | shortest_path(graph, i[1], end_point), |
||
| 82 | ] |
||
| 83 | unpack_routes_metric = [ |
||
| 84 | len(i[0]) * 2 + len(i[1]) + len(i[2]) * 2 |
||
| 85 | for i in sorted(unpack_routes.values()) |
||
| 86 | ] |
||
| 87 | if len(direct_route) * 2 < min(unpack_routes_metric): |
||
| 88 | route = direct_route |
||
| 89 | else: |
||
| 90 | index = unpack_routes_metric.index(min(unpack_routes_metric)) |
||
| 91 | route = ''.join(sorted(unpack_routes.values())[index]) |
||
| 92 | return route |
||
| 93 | |||
| 94 | |||
| 95 | if __name__ == '__main__': # pragma: no cover |
||
| 96 | print("Example:") |
||
| 97 | print(checkio(["S...", "....", "B.WB", "..WE"])) |
||
| 98 | |||
| 99 | # This part is using only for self-checking and not necessary for auto-testing |
||
| 100 | ACTIONS = {"L": (0, -1), "R": (0, 1), "U": (-1, 0), "D": (1, 0), "B": (0, 0)} |
||
| 101 | |||
| 102 | def check_solution(func, max_time, field): |
||
| 103 | max_row, max_col = len(field), len(field[0]) |
||
| 104 | s_row, s_col = 0, 0 |
||
| 105 | total_time = 0 |
||
| 106 | hold_box = True |
||
| 107 | route = func(field[:]) |
||
| 108 | for step in route: |
||
| 109 | if step not in ACTIONS: |
||
| 110 | print("Unknown action {0}".format(step)) |
||
| 111 | return False |
||
| 112 | View Code Duplication | if step == "B": |
|
| 113 | if hold_box: |
||
| 114 | if field[s_row][s_col] == "B": |
||
| 115 | hold_box = False |
||
| 116 | total_time += 1 |
||
| 117 | continue |
||
| 118 | else: |
||
| 119 | print("Stephan broke the cargo") |
||
| 120 | return False |
||
| 121 | else: |
||
| 122 | if field[s_row][s_col] == "B": |
||
| 123 | hold_box = True |
||
| 124 | total_time += 1 |
||
| 125 | continue |
||
| 126 | n_row, n_col = s_row + ACTIONS[step][0], s_col + ACTIONS[step][1] |
||
| 127 | total_time += 2 if hold_box else 1 |
||
| 128 | if 0 > n_row or n_row >= max_row or 0 > n_col or n_row >= max_col: |
||
| 129 | print("We've lost Stephan.") |
||
| 130 | return False |
||
| 131 | if field[n_row][n_col] == "W": |
||
| 132 | print("Stephan fell in water.") |
||
| 133 | return False |
||
| 134 | s_row, s_col = n_row, n_col |
||
| 135 | if field[s_row][s_col] == "E" and hold_box: |
||
| 136 | if total_time <= max_time: |
||
| 137 | return True |
||
| 138 | else: |
||
| 139 | print("You can deliver the cargo faster.") |
||
| 140 | return False |
||
| 141 | print("The cargo is not delivered") |
||
| 142 | return False |
||
| 143 | |||
| 144 | assert check_solution(checkio, 12, ["S...", "....", "B.WB", "..WE"]), "1st Example" |
||
| 145 | assert check_solution(checkio, 11, ["S...", "....", "B..B", "..WE"]), "2nd example" |
||
| 146 | print("Coding complete? Click 'Check' to earn cool rewards!") |
||
| 147 |