From 908184699b456d80d3f3b6d20cf564a203e5aa98 Mon Sep 17 00:00:00 2001 From: Dnomd343 Date: Sat, 25 Jan 2025 16:24:57 +0800 Subject: [PATCH] update: serial number of solutions --- verify/classic.py | 257 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 213 insertions(+), 44 deletions(-) diff --git a/verify/classic.py b/verify/classic.py index fbbe68f..7706dba 100755 --- a/verify/classic.py +++ b/verify/classic.py @@ -1,10 +1,10 @@ #!/usr/bin/env python3 import json -from enum import Enum import functools +from enum import Enum from pprint import pprint -from klotski import Group, Layout +from klotski import Block, Group, Layout @functools.cache @@ -19,16 +19,17 @@ class Solution: Z = 2 @staticmethod - def __solu_info(layout: str) -> tuple[int, int]: # (max_step, ethnic_size) + def __solu_info(layout: str) -> tuple[int, int]: # (ethnic_deep, ethnic_size) cases = [x for x, y in global_data().items() if layout in y['solutions']] steps = [global_data()[x]['step'] for x in cases] assert min(steps) == 0 return max(steps), len(cases) def __init__(self, code: str, solu_type: Type): + self.__name = '' self.__code = code self.__type = solu_type - self.__max_step, self.__ethnic_size = Solution.__solu_info(code) + self.__ethnic_deep, self.__ethnic_size = Solution.__solu_info(code) @property def code(self) -> str: @@ -38,6 +39,14 @@ class Solution: def type(self) -> Type: return self.__type + @property + def name(self) -> str: + return self.__name + + @name.setter + def name(self, name: str) -> None: + self.__name = name + @property def group(self) -> Group: return self.layout.group @@ -47,21 +56,22 @@ class Solution: return Layout(self.code) @property - def max_step(self) -> int: - return self.__max_step + def ethnic_deep(self) -> int: + return self.__ethnic_deep @property def ethnic_size(self) -> int: return self.__ethnic_size def __repr__(self): - val = f'{self.ethnic_size}, {self.max_step}' - return f'{self.code} {self.type.name} ({val})' + return (f'') class SoluGroup: def __init__(self, name: str): - self.__name = name + self.__name = '' + self.__inner_name = name # klotski group name self.__x_solus: list[Solution] = [] self.__y_solus: list[Solution] = [] self.__z_solus: list[Solution] = [] @@ -78,6 +88,14 @@ class SoluGroup: def name(self) -> str: return self.__name + @name.setter + def name(self, name: str) -> None: + self.__name = name + + @property + def inner_name(self) -> str: + return self.__inner_name + @property def solus(self) -> list[Solution]: return self.__x_solus + self.__y_solus + self.__z_solus @@ -94,13 +112,32 @@ class SoluGroup: def group_size(self) -> int: return self.group.size + @property + def group_valid_size(self) -> int: + + def is_invalid_solu(layout: Layout) -> bool: + if str(layout)[0] != 'D': # 2x2 block address not 13 + return False + block_seq = layout.dump_seq() + is_type_x = block_seq[12] == Block.SPACE and block_seq[16] == Block.SPACE + is_type_y = block_seq[15] == Block.SPACE and block_seq[19] == Block.SPACE + is_type_z = block_seq[9] == Block.SPACE and block_seq[10] == Block.SPACE + return not (is_type_x or is_type_y or is_type_z) + + num = len([x for x in list(self.group.cases()) if is_invalid_solu(x)]) + return self.group_size - num + @property def ethnic_sizes(self) -> list[int]: return sorted([x.ethnic_size for x in self.solus], key=lambda x: -x) @property def max_ethnic_size(self) -> int: - return max(self.ethnic_sizes) + return max(x.ethnic_size for x in self.solus) + + @property + def max_ethnic_deep(self) -> int: + return max(x.ethnic_deep for x in self.solus) @property def min_case(self) -> Layout: @@ -111,63 +148,195 @@ class SoluGroup: return min(x.layout for x in self.solus) def __repr__(self): - return (f' {self.ethnic_sizes}>') + return (f' {self.ethnic_sizes}>') + + def build_m_index(self) -> None: + assert self.__name[-1] == 'M' + + assert len(self.__x_solus) == len(self.__y_solus) + self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) + y_index = {str(y.layout.to_horizontal_mirror())[:7]: x for x, y in enumerate(self.__x_solus)} + self.__y_solus.sort(key=lambda x: y_index[x.code]) + + for num in range(len(self.__x_solus)): + self.__x_solus[num].name = f'{self.name}-{num:03}X' + self.__y_solus[num].name = f'{self.name}-{num:03}Y' + + z_map = {x.code: x for x in self.__z_solus} + z_pairs = [tuple(sorted([x.layout, x.layout.to_horizontal_mirror()])) for x in self.__z_solus] + z_pairs = [(z_map[str(x)[:7]], z_map[str(y)[:7]]) for x, y in set(z_pairs)] + z_pairs.sort(key=lambda x: (-x[0].ethnic_size, -x[0].ethnic_deep, x[0].layout)) + + for num in range(len(z_pairs)): + if z_pairs[num][0] == z_pairs[num][1]: + z_pairs[num][0].name = f'{self.name}-{num:02}Z' + else: + z_pairs[num][0].name = f'{self.name}-{num:02}ZL' + z_pairs[num][1].name = f'{self.name}-{num:02}ZR' + + def build_lx_index(self) -> dict[str, str]: + assert self.__name[-1] == 'L' + right_data = {} + self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) + for num in range(len(self.__x_solus)): + self.__x_solus[num].name = f'{self.name}-{num:01}X' + mirror = str(self.__x_solus[num].layout.to_horizontal_mirror())[:7] + right_data[mirror] = f'{num:01}Y' + return right_data + + def build_rx_index(self) -> dict[str, str]: + assert self.__name[-1] == 'R' + left_data = {} + self.__x_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) + for num in range(len(self.__x_solus)): + self.__x_solus[num].name = f'{self.name}-{num:01}X' + mirror = str(self.__x_solus[num].layout.to_horizontal_mirror())[:7] + left_data[mirror] = f'{num:01}Y' + return left_data + + def apply_ly_index(self, data: dict[str, str]) -> None: + assert self.__name[-1] == 'L' + for solu in self.__y_solus: + solu.name = f'{self.name}-{data[solu.code]}' + + def apply_ry_index(self, data: dict[str, str]) -> None: + assert self.__name[-1] == 'R' + for solu in self.__y_solus: + solu.name = f'{self.name}-{data[solu.code]}' + + def build_lz_index(self) -> dict[str, str]: + assert self.__name[-1] == 'L' + data = {} + self.__z_solus.sort(key=lambda x: (-x.ethnic_size, -x.ethnic_deep, x.layout)) + for num in range(len(self.__z_solus)): + self.__z_solus[num].name = f'{self.name}-{num:01}Z' + mirror = str(self.__z_solus[num].layout.to_horizontal_mirror())[:7] + data[mirror] = f'{num:01}Z' + return data + + def apply_rz_index(self, data: dict[str, str]) -> None: + assert self.__name[-1] == 'R' + for solu in self.__z_solus: + solu.name = f'{self.name}-{data[solu.code]}' class SoluPattern: def __init__(self, *solu_groups: SoluGroup): + self.__pattern_id = -1 + inner_type_id = solu_groups[0].inner_name[:3] + self.__type_id = {'174': 0, '169': 1, '164': 2, '159': 3, '154': 4, '149': 5}[inner_type_id] + assert len(solu_groups) in [1, 2] if len(solu_groups) == 1: self.__is_single = True - self.__solu_m = solu_groups[0] + self.__group_m = solu_groups[0] else: self.__is_single = False - self.__solu_l, self.__solu_r = solu_groups # TODO: confirm l/r - assert self.__solu_l.name[:-1] == self.__solu_r.name[:-1] - assert self.__solu_l.solu_num == self.__solu_r.solu_num - assert self.__solu_l.group_size == self.__solu_r.group_size - assert self.__solu_l.ethnic_sizes == self.__solu_r.ethnic_sizes + if solu_groups[0].min_solu < solu_groups[1].min_solu: + self.__group_l, self.__group_r = solu_groups + else: + self.__group_r, self.__group_l = solu_groups + assert self.__group_l.inner_name[:-1] == self.__group_r.inner_name[:-1] + assert self.__group_l.solu_num == self.__group_r.solu_num + assert self.__group_l.group_size == self.__group_r.group_size + assert self.__group_l.ethnic_sizes == self.__group_r.ethnic_sizes + + @property + def type_id(self) -> int: + return self.__type_id + + @property + def pattern_id(self) -> int: + return self.__pattern_id + + @pattern_id.setter + def pattern_id(self, id_val: int) -> None: + self.__pattern_id = id_val + if self.__is_single: + self.__group_m.name = f'{self.type_id}-{self.pattern_id:02}M' + else: + self.__group_l.name = f'{self.type_id}-{self.pattern_id:02}L' + self.__group_r.name = f'{self.type_id}-{self.pattern_id:02}R' @property def pattern_size(self) -> int: if self.__is_single: - return self.__solu_m.group_size - return self.__solu_l.group_size * 2 + return self.__group_m.group_size + return self.__group_l.group_size * 2 + + @property + def pattern_valid_size(self) -> int: + if self.__is_single: + return self.__group_m.group_valid_size + assert self.__group_l.group_valid_size == self.__group_r.group_valid_size + return self.__group_l.group_valid_size * 2 @property def ethnic_sizes(self) -> list[int]: if self.__is_single: - return self.__solu_m.ethnic_sizes - return self.__solu_l.ethnic_sizes + return self.__group_m.ethnic_sizes + assert self.__group_l.ethnic_sizes == self.__group_r.ethnic_sizes + return self.__group_l.ethnic_sizes @property def max_ethnic_size(self) -> int: - return max(self.ethnic_sizes) + if self.__is_single: + return self.__group_m.max_ethnic_size + assert self.__group_l.max_ethnic_size == self.__group_r.max_ethnic_size + return self.__group_l.max_ethnic_size + + @property + def max_ethnic_deep(self) -> int: + if self.__is_single: + return self.__group_m.max_ethnic_deep + assert self.__group_l.max_ethnic_deep == self.__group_r.max_ethnic_deep + return self.__group_l.max_ethnic_deep @property def min_case(self) -> Layout: if self.__is_single: - return self.__solu_m.min_case + return self.__group_m.min_case else: - return min(self.__solu_l.min_case, self.__solu_r.min_case) + return min(self.__group_l.min_case, self.__group_r.min_case) @property def min_solu(self) -> Layout: if self.__is_single: - return self.__solu_m.min_solu + return self.__group_m.min_solu + else: + return min(self.__group_l.min_solu, self.__group_r.min_solu) + + @property + def solus(self) -> list[Solution]: + if self.__is_single: + return self.__group_m.solus else: - return min(self.__solu_l.min_solu, self.__solu_r.min_solu) + return self.__group_l.solus + self.__group_r.solus def __repr__(self): if self.__is_single: - name = self.__solu_m.name - solu_num = f'{self.__solu_m.solu_num}' + name = self.__group_m.name + inner_name = self.__group_m.inner_name + solu_num = f'{self.__group_m.solu_num}' + group_repr = f'{self.__group_m}' else: - name = f'{self.__solu_l.name}|{self.__solu_r.name}' - solu_num = f'{self.__solu_l.solu_num}*2' - return (f' {self.ethnic_sizes}>') + name = f'{self.__group_l.name}|{self.__group_r.name}' + inner_name = f'{self.__group_l.inner_name}|{self.__group_r.inner_name}' + solu_num = f'{self.__group_l.solu_num}*2' + group_repr = f'{self.__group_l}\n{self.__group_r}' + return (f' {self.ethnic_sizes}>\n{group_repr}') + + def build_index(self) -> None: + if self.__is_single: + self.__group_m.build_m_index() + else: + self.__group_r.apply_ry_index(self.__group_l.build_lx_index()) + self.__group_l.apply_ly_index(self.__group_r.build_rx_index()) + self.__group_r.apply_rz_index(self.__group_l.build_lz_index()) def load_valid_solutions(num: int) -> list[Solution]: @@ -178,7 +347,7 @@ def load_valid_solutions(num: int) -> list[Solution]: return data_x + data_y + data_z -def split_into_groups(solus: list[Solution]) -> list[SoluPattern]: +def split_patterns(solus: list[Solution]) -> list[SoluPattern]: groups = {} for solu in solus: group = str(solu.group) @@ -200,21 +369,21 @@ def split_into_groups(solus: list[Solution]) -> list[SoluPattern]: for pattern_tuple in sorted(set(pattern_tuples)): patterns.append(SoluPattern(*[groups[x] for x in pattern_tuple])) - return sorted(patterns, key=lambda x: (-x.pattern_size, -x.max_ethnic_size, x.min_solu)) + patterns.sort(key=lambda x: (-x.pattern_valid_size, -x.pattern_size, -x.max_ethnic_size, x.min_solu)) + for pattern_id, pattern in enumerate(patterns): + pattern.pattern_id = pattern_id + pattern.build_index() + return patterns def main() -> None: - - # solu = Solution('DAAF4CC', Solution.Type.X) - # print(solu) - - # for solu_group in split_into_groups(load_valid_solutions(1)): - # print(solu_group) - + solus = [] for num in range(6): # 0/1/2/3/4/5 - for solu_group in split_into_groups(load_valid_solutions(num)): - print(solu_group) + for solu_pattern in split_patterns(load_valid_solutions(num)): + solus.extend(solu_pattern.solus) + print(solu_pattern, end='\n\n') print('-' * 238) + pprint(solus) if __name__ == "__main__":