bats_bunker.path_coordinates()   D
last analyzed

Complexity

Conditions 12

Size

Total Lines 41
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 33
nop 2
dl 0
loc 41
rs 4.8
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like bats_bunker.path_coordinates() 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 combinations
4
from math import floor, hypot
5
6
7 View Code Duplication
def shortestPath(graph, start, end):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
8
    queue = [(0, start, [])]
9
    seen = set()
10
    while True:
11
        (cost, v, path) = heapq.heappop(queue)
12
        if v not in seen:
13
            path = path + [v]
14
            seen.add(v)
15
            if v == end:
16
                return cost, path
17
            for (next_item, c) in graph[v].items():
18
                heapq.heappush(queue, (cost + c, next_item, path))
19
    return queue
20
21
22
def find_entities(bunker, entity):
23
    positions = []
24
    for y in range(len(bunker)):
25
        for x in range(len(bunker[y])):
26
            if bunker[y][x] == entity:
27
                positions.append((x, y))
28
    return positions
29
30
31
def numbers(x, y):
32
    if x <= y:
33
        return range(0, y - x + 1)
34
    else:
35
        return range(0, y - x - 1, -1)
36
37
38
def get_slope(position_1, position_2):
39
    return (position_2[1] - position_1[1]) / (position_2[0] - position_1[0])
40
41
42
def path_coordinates(position_1, position_2):
43
    path = []
44
    if position_2[0] == position_1[0]:
45
        # vertical
46
        for i in numbers(position_1[1], position_2[1]):
47
            path.append((position_1[0], position_1[1] + i))
48
    elif position_2[1] == position_1[1]:
49
        # horizontal
50
        for i in numbers(position_1[0], position_2[0]):
51
            path.append((position_1[0] + i, position_1[1]))
52
    else:
53
        slope = get_slope(position_1, position_2)
54
        if abs(slope) == 1:
55
            # diagnal
56
            for i in numbers(position_1[0], position_2[0]):
57
                path.append((position_1[0] + i, round(position_1[1] + i * slope)))
58
                # this is an edge case that the bat will hit the corner of a wall
59
                if len(path) > 1 and get_slope(path[-2], path[-1]) in [1, -1]:
60
                    path = [
61
                        (path[-2][0], path[-1][1]),
62
                        (path[-1][0], path[-2][1]),
63
                    ] + path
64
        else:
65
            # get the minimal step
66
            steps_per_cell = int(max(abs(slope), abs(1 / slope)))
67
            x_steps = abs(position_2[0] - position_1[0]) * steps_per_cell
68
            y_steps = abs(position_2[1] - position_1[1]) * steps_per_cell
69
            steps = max(x_steps, y_steps) * 2
70
            x_progress = (position_2[0] - position_1[0]) / steps
71
            y_progress = (position_2[1] - position_1[1]) / steps
72
            for i in range(steps + 1):
73
                new_x = position_1[0] + i * x_progress
74
                new_y = position_1[1] + i * y_progress
75
                path.append((round(new_x), round(new_y)))
76
                # hit the conner
77
                if new_x - int(new_x) == 0.5 and new_y - int(new_y) == 0.5:
78
                    path.append((round(new_x - 0.1), round(new_y - 0.1)))
79
                    path.append((round(new_x - 0.1), round(new_y + 0.1)))
80
                    path.append((round(new_x + 0.1), round(new_y - 0.1)))
81
                    path.append((round(new_x + 0.1), round(new_y + 0.1)))
82
    return list(set(path))
83
84
85
def bats_visibility(BatPositions, bunker):
86
    walls = find_entities(bunker, 'W')
87
    visibility = defaultdict(dict)
88
    for i, j in combinations(BatPositions, 2):
89
        path = path_coordinates(i, j)
90
        if not set(path).intersection(set(walls)):
91
            distance = hypot(i[0] - j[0], i[1] - j[1])
92
            visibility[i][j] = distance
93
            visibility[j][i] = distance
94
    return visibility
95
96
97
def checkio(bunker):
98
    bats = find_entities(bunker, 'B')
99
    alpha = find_entities(bunker, 'A')
100
    all_bats = bats + alpha
101
    visibility = bats_visibility(all_bats, bunker)
102
    return shortestPath(visibility, alpha[0], (0, 0))[0]
103
104
105
# These "asserts" using only for self-checking and not necessary for
106
# auto-testing
107
if __name__ == '__main__':
108
109
    def almost_equal(checked, correct, significant_digits=2):
110
        precision = 0.1 ** significant_digits
111
        return correct - precision < checked < correct + precision
112
113
    assert almost_equal(checkio(["B--", "---", "--A"]), 2.83), "1st example"
114
    assert almost_equal(checkio(["B-B", "BW-", "-BA"]), 4), "2nd example"
115
    assert almost_equal(checkio(["BWB--B", "-W-WW-", "B-BWAB"]), 12), "3rd example"
116
    assert almost_equal(
117
        checkio(["B---B-", "-WWW-B", "-WA--B", "-W-B--", "-WWW-B", "B-BWB-"]), 9.24
118
    ), "4th example"
119