magic_domino.check_data()   F
last analyzed

Complexity

Conditions 17

Size

Total Lines 58
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 17
eloc 36
nop 3
dl 0
loc 58
rs 1.8
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like magic_domino.check_data() 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 operator as op
2
from functools import reduce
3
from itertools import chain, combinations, islice, permutations, product
4
from random import shuffle
5
6
7
flatten = lambda x: [j for i in x for j in i]
8
9
10
def dominos():
11
    return [(i, j) for i in range(7) for j in range(i, 7)]
12
13
14
def tile_combinations(tiles, size, number):
15
    return [i for i in combinations(tiles, size // 2) if sum(map(sum, i)) == number]
16
17
18
def combine_columns(columns, size, exception=set()):
19
    if size == 0:
20
        yield []
21
    for i, v in enumerate(columns):
22
        new_exception = set(v) | exception
23
        sub_columns = [
24
            item
25
            for item in columns[i + 1 :]
26
            if not any([tile in new_exception for tile in item])
27
        ]
28
        for j in combine_columns(sub_columns, size - 1, new_exception):
29
            ret = [v] + j
30
            yield ret
31
32
33
def row_sum_check(columns, number, size):
34
    for i in range(size):
35
        item_index, sub_item_index = i // 2, i % 2
36
        if sum([i[item_index][sub_item_index] for i in columns]) != number:
37
            return False
38
    return True
39
40
41
def diagonal_check(columns, number, size):
42
    diagonal_indexes = []
43
    reversed_diagonal_indexes = []
44
    for i in range(size):
45
        diagonal_indexes.append((i // 2, i % 2))
46
        reversed_diagonal_indexes.append(((size - 1 - i) // 2, (size - 1 - i) % 2))
47
48
    for i in permutations(columns):
49
        if (
50
            sum(
51
                [
52
                    value[diagonal_indexes[row][0]][diagonal_indexes[row][1]]
53
                    for row, value in enumerate(i)
54
                ]
55
            )
56
            == number
57
            and sum(
58
                [
59
                    value[reversed_diagonal_indexes[row][0]][
60
                        reversed_diagonal_indexes[row][1]
61
                    ]
62
                    for row, value in enumerate(i)
63
                ]
64
            )
65
            == number
66
        ):
67
            return i
68
    return False
69
70
71
def brute_force(domino, number, size):
72
    shuffled_cols = []
73
    for column in domino:
74
        element_list = []
75
        for element in column:
76
            element_list.append(list(set([element, element[::-1]])))
77
78
        # find out all possible permutations of a column
79
        col_possibilities = []
80
        for i in permutations(element_list, len(element_list)):
81
            col_possibilities.append(product(*i))
82
        col_possibilities = chain(*col_possibilities)
83
        shuffled_cols.append(col_possibilities)
84
85
    for i in product(*shuffled_cols):
86
        if row_sum_check(i, number, size):
87
            ret = diagonal_check(i, number, size)
88
            if ret:
89
                return ret
90
91
92
def magic_domino(size, number):
93
    domino_tiles = dominos()
94
    columns = tile_combinations(domino_tiles, size, number)
95
    for i in combine_columns(columns, size):
96
        ret = brute_force(i, number, size)
97
        if ret:
98
            return list(zip(*[flatten(j) for j in ret]))
99
100
101
if __name__ == '__main__':
102
    # These "asserts" using only for self-checking and not necessary for auto-testing
103
    import itertools
104
105
    def check_data(size, number, user_result):
106
107
        # check types
108
        def check_container_type(o):
109
            return any(map(lambda t: isinstance(o, t), (list, tuple)))
110
111
        def check_cell_type(i):
112
            return isinstance(i, int)
113
114
        if not (
115
            check_container_type(user_result)
116
            and all(map(check_container_type, user_result))
117
            and all(map(lambda row: all(map(check_cell_type, row)), user_result))
118
        ):
119
            raise Exception(
120
                "You should return a list/tuple of lists/tuples with integers."
121
            )
122
123
        # check sizes
124
        def check_size(o):
125
            return len(o) == size
126
127
        if not (check_size(user_result) and all(map(check_size, user_result))):
128
            raise Exception("Wrong size of answer.")
129
130
        # check is it a possible numbers (from 0 to 6 inclusive)
131
        if not all(
132
            map(lambda x: 0 <= x <= 6, itertools.chain.from_iterable(user_result))
133
        ):
134
            raise Exception("Wrong matrix integers (can't be domino tiles)")
135
136
        # check is it a magic square
137
        def seq_sum_check(seq):
138
            return sum(seq) == number
139
140
        diagonals_indexes = zip(
141
            *map(lambda i: ((i, i), (i, size - i - 1)), range(size))
142
        )
143
144
        def values_from_indexes(inds):
145
            return itertools.starmap(lambda x, y: user_result[y][x], inds)
146
147
        if not (
148
            all(map(seq_sum_check, user_result))
149
            and all(map(seq_sum_check, zip(*user_result)))  # rows
150
            and all(  # columns
151
                map(seq_sum_check, map(values_from_indexes, diagonals_indexes))
152
            )
153
        ):  # diagonals
154
            raise Exception("It's not a magic square.")
155
156
        # check is it domino square
157
        tiles = set()
158
        for x, y in itertools.product(range(size), range(0, size, 2)):
159
            tile = tuple(sorted((user_result[y][x], user_result[y + 1][x])))
160
            if tile in tiles:
161
                raise Exception("It's not a domino magic square.")
162
            tiles.add(tile)
163
164
    check_data(4, 5, magic_domino(4, 5))
165