express_delivery   A
last analyzed

Complexity

Total Complexity 42

Size/Duplication

Total Lines 147
Duplicated Lines 18.37 %

Importance

Changes 0
Metric Value
eloc 121
dl 27
loc 147
rs 9.0399
c 0
b 0
f 0
wmc 42

6 Functions

Rating   Name   Duplication   Size   Complexity  
A find_spots() 0 3 1
D build_topology() 0 27 12
A checkio() 0 24 3
F check_solution() 14 41 16
A shortest_path() 13 13 5
A translate_route() 0 12 5

How to fix   Duplicated Code    Complexity   

Duplicated Code

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:

Complexity

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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":
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
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