欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

七巧板拼接

程序员文章站 2022-03-03 11:13:05
...

知识点:

1. 求凸包

2. 平面图形的旋转和翻转,矩阵运算

3.求向量夹角,判断拼接是否有重复

 

代码:

import copy
import numpy as np


# get colours and coordinates
def available_coloured_pieces(file_name):
    # get all coordinates from file
    coloured_pieces = []  # store coordinates
    line = file_name.readline()
    while line:
        line_tmp = copy.deepcopy(line)  # deepcopy for using split() function
        line_tmp_split = line_tmp.split()
        if len(line_tmp_split) > 0 and line_tmp_split[0] == "<path":
            shape = [int(s) for s in line_tmp_split if s.isdigit()]  # <class 'list'>: [30, 20, 110, 20, 110, 120, 30, 120]
            shape_color = []  # <class 'list'>: ["red", (30, 20), (110, 20), (110, 120), (30, 120)]
            shape_color.append(line_tmp.split('"')[-2])
            for i in range(len(shape))[::2]:
                point = (shape[i], shape[i+1])
                shape_color.append(point)
            coloured_pieces.append(shape_color)  # store each shape's coordinates
        line = file_name.readline()

    return coloured_pieces


# ------------------First Task-----------------------
# get base point index
def get_bottom_left_point(p):
    k = 0
    for i in range(1, len(p)):
        if p[i][1] < p[k][1] or (p[i][1] == p[k][1] and p[i][0] < p[k][0]):
            k = i
    return k


# get cross product value
def multiply(p1, p2, p0):
    x0 = p0[0]
    y0 = p0[1]
    x1 = p1[0]
    y1 = p1[1]
    x2 = p2[0]
    y2 = p2[1]
    return (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0)


# get polar angle
def get_angle(p1, p0):
    x0 = p0[0]
    y0 = p0[1]
    x1 = p1[0]
    y1 = p1[1]
    if x1 - x0 == 0:
        if y1 - y0 == 0:
            return -1
        else:
            return np.pi / 2
    tan = float((y1 - y0) / (x1 - x0))
    arc = np.arctan(tan)
    if arc < 0:
        arc = np.pi + arc
    return arc


# sort points by polar angle, not include base point
def sort_points(p, base_point):
    p2 = []
    for i in range(len(p)):
        p2.append({"index": i, "arc": get_angle(p[i], base_point)})
    p2.sort(key=lambda k: (k.get("arc")))
    p_out = []
    for i in range(len(p2)):
        p_out.append(p[p2[i]["index"]])
    return p_out


def get_convex(p):
    base_index = get_bottom_left_point(p)
    base_point = p[base_index]
    p.remove(base_point)
    p_sort = sort_points(p, base_point)
    p_result = [base_point, p_sort[0]]
    for i in range(1, len(p_sort)):
        while multiply(p_result[-2], p_sort[i], p_result[-1]) > 0:
            p_result.pop()
        p_result.append(p_sort[i])
    return p_result


def check_convex(list_orig):
    list_cut = list_orig[1:]
    num = len(list_cut)
    if num < 3:
        return False
    list_unique = list(set(list_cut))
    if len(list_unique) != num:
        return False
    list_convex = get_convex(list_unique)
    if len(list_convex) != num:
        return False

    begin_idx = list_cut.index(list_convex[0])
    anti_clockwise = True
    for i in range(num):
        if list_convex[i] != list_cut[(begin_idx + i) % num]:
            anti_clockwise = False
            break

    clockwise = True
    for i in range(num):
        if list_convex[i] != list_cut[(begin_idx - i + num) % num]:
            clockwise = False
            break

    return clockwise or anti_clockwise


def are_valid(pieces):
    convex_list = []  # [True, False, False]
    for piece in pieces:  # iterate through every piece
        convex_list.append(check_convex(piece))

    for b in convex_list:  # [True, False, False]
        if not b:
            return False
    return True


# ------------------Second Task-----------------------
def normal(piece):  # [(50, 50), (50, 90), (90, 90)]
    num = len(piece)  # num of vertex
    piece_norm = []
    x_avg = sum(piece[i][0] for i in range(num))/len(piece)
    y_avg = sum(piece[i][1] for i in range(num))/len(piece)

    for i in range(num):
        piece_norm.append((piece[i][0]-x_avg, piece[i][1]-y_avg))

    return piece_norm


def is_rotation(ans):
    epsilon = 1e-5  # infinitely small
    if abs(ans[0][0] - ans[1][1]) < epsilon and abs(ans[1][0] + ans[0][1]) < epsilon and abs(ans[0][0]**2 + ans[0][1]**2 - 1) < epsilon:
    # if ans[0][0] == ans[1][1] and ans[1][0] == -ans[0][1] and ans[0][0] ** 2 + ans[0][1] ** 2 == 1:  # wrong for accuracy is not enough
        return True
    else:
        return False


def are_identical_sets_of_coloured_pieces(pieces_1, pieces_2):
    # pieces_1  [ ['red', (50, 50), (50, 90), (90, 90)],
    #             ['green', (160, 170), (160, 130), (120, 130)],
    #             ...
    #            ]
    if len(pieces_1) != len(pieces_2):  # num of pieces must be equal
        return False

    pieces_1.sort()
    pieces_2.sort()

    for i in range(len(pieces_1)):
        if pieces_1[i][0] != pieces_2[i][0]:
            return False
        piece1 = pieces_1[i][1:]  # [(50, 50), (50, 90), (90, 90)]
        piece2 = pieces_2[i][1:]  # [(200, 30), (180, 30), (180, 50), (220, 50)]

        if len(piece1) != len(piece2):
            return False

        piece1_norm = normal(piece1)  # move the center of gravity to the origin
        piece2_norm = normal(piece2)  # [(5.0, -10.0), (-15.0, -10.0), (-15.0, 10.0), (25.0, 10.0)]

        piece2_norm = piece2_norm + piece2_norm

        # no flip case
        x1 = []
        y1 = []
        for j in range(len(piece1_norm)):
            x1.append(piece1_norm[j][0])
            y1.append(piece1_norm[j][1])
        piece1_norm_cal = np.array([x1, y1])

        x2 = []
        y2 = []
        for k in range(len(piece2_norm)):
            x2.append(piece2_norm[k][0])
            y2.append(piece2_norm[k][1])
        piece2_norm_cal = np.array([x2, y2])

        piece2_norm_cal_anti_wise = np.array([x2[::-1], y2[::-1]])

        idx = False
        # flip case
        flip_mat = np.array([[1, 0],
                             [0, -1]])  # upside down
        piece1_norm_cal_flip = flip_mat.dot(piece1_norm_cal)
        num_vertex = len(piece1_norm_cal[0])  # num of vertex
        for n in range(num_vertex):
            clock_ans = piece2_norm_cal[:, n:n+num_vertex].dot(np.linalg.pinv(piece1_norm_cal))  # no flip case
            clock_ans_flip = piece2_norm_cal[:, n:n+num_vertex].dot(np.linalg.pinv(piece1_norm_cal_flip))  # flip case
            anti_clock_ans = piece2_norm_cal_anti_wise[:, n:n+num_vertex].dot(np.linalg.pinv(piece1_norm_cal))  # no flip case
            anti_clock_ans_flip = piece2_norm_cal_anti_wise[:, n:n+num_vertex].dot(np.linalg.pinv(piece1_norm_cal_flip))  # flip case
            is_rotation_clock = is_rotation(clock_ans)
            is_rotation_clock_flip = is_rotation(clock_ans_flip)
            is_rotation_anti_clock = is_rotation(anti_clock_ans)
            is_rotation_anti_clock_flip = is_rotation(anti_clock_ans_flip)
            if is_rotation_clock or is_rotation_clock_flip or is_rotation_anti_clock or is_rotation_anti_clock_flip:
                idx = True
                break

        if not idx:
            return False

    return True


# ------------------Third Task-----------------------
def is_alignment(tangram, shape):
    point_angle = {}
    point_vector = {}
    eps = 1e-5
    for piece in tangram:
        points = piece[1:]
        num = len(points)
        for i in range(num):
            if points[i] in shape:
                continue
            x0 = points[(i - 1 + num) % num][0]
            y0 = points[(i - 1 + num) % num][1]
            x1 = points[i][0]
            y1 = points[i][1]
            x2 = points[(i + 1) % num][0]
            y2 = points[(i + 1) % num][1]
            a = np.array([x0 - x1, y0 - y1])
            b = np.array([x2 - x1, y2 - y1])
            La = np.sqrt(a.dot(a))
            Lb = np.sqrt(b.dot(b))
            a_norm = tuple(a / La)
            b_norm = tuple(b / Lb)
            if La * Lb == 0:
                return False
            cos_angle = a.dot(b) / (La * Lb)
            angle = np.arccos(cos_angle)
            if points[i] not in point_angle:
                point_angle[points[i]] = angle
            else:
                point_angle[points[i]] += angle

            if points[i] not in point_vector:
                point_vector[points[i]] = {a_norm: 1, b_norm: 1}
            else:
                if a_norm not in point_vector[points[i]]:
                    point_vector[points[i]][a_norm] = 1
                else:
                    point_vector[points[i]][a_norm] += 1
                    if point_vector[points[i]][a_norm] > 2:
                        return False
                if b_norm not in point_vector[points[i]]:
                    point_vector[points[i]][b_norm] = 1
                else:
                    point_vector[points[i]][b_norm] += 1
                    if point_vector[points[i]][b_norm] > 2:
                        return False

    for k in point_angle:
        if not (abs(point_angle[k] - np.pi) < eps or abs(point_angle[k] - np.pi * 2) < eps):
            return False
    return True


def is_solution(tangram, shape):
    # tangram: [['green', (30, 60), (30, 20), (70, 20)],
    #           ['purple', (50, 120), (50, 80), (70, 60), (90, 80), (90, 120)],
    #           ['olive', (30, 100), (90, 40), (70, 20), (30, 60)],
    #           ['magenta', (70, 60), (110, 100), (110, 60), (90, 40)],
    #           ['blue', (50, 120), (30, 120), (30, 100), (50, 80)],
    #           ['red', (110, 20), (70, 20), (110, 60)],
    #           ['yellow', (110, 100), (110, 120), (90, 120), (90, 80)]]
    # shape:   [['grey', (30, 20), (110, 20), (110, 120), (30, 120)]]

    # no correct tangram and shape
    if len(shape) != 1:
        return False
    if len(shape[0]) < 4:  # colour and points
        return False
    for p in tangram:
        if len(p) < 4:
            return False

    # shape's points
    shape_points = shape[0][1:]

    # # tangram's points that not belong to shape's points
    # piece_set = set()  # remove duplicate
    # for piece in tangram:
    #     for piece_point in piece[1:]:
    #         if piece_point not in shape_points:
    #             piece_set.add(piece_point)
    #
    # # judge if the points are inside the shape
    # for point in piece_set:
    #     bool_inside = is_inside(point, shape_points)
    #     if not bool_inside:
    #         return False

    if is_alignment(tangram, shape_points):
        return True
    else:
        return False