def how_to_elimionation_only_memo(cls, how_anlz: HowToAnalyze) -> Msg: """消去法(メモがその枡にしかない)メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 Returns: Msg: メッセージ """ return Msg( MsgType.SUCCESS, cls._get_msg(MsgCode.HOW_TO_ELIMIONATION_ONLY_MEMO).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), region=SudokuUtil.cnv_region_to_text(how_anlz.region), commitVal=how_anlz.commit_val))
def dup_row(cls, pivot_squ: Square, compare_squ: Square) -> Msg: """行重複メッセージ生成 Args: pivot_squ (Square): 基準枡 compare_squ (Square): 比較枡 Returns: Msg: メッセージ """ return Msg( MsgType.ERROR, cls._get_msg(MsgCode.DUP_ROW).format( val=pivot_squ.get_fixed_val(), pivotSqu=SudokuUtil.cnv_squ_to_text(pivot_squ), compareSqu=SudokuUtil.cnv_squ_to_text(compare_squ)))
def how_to_elimionation(cls, how_anlz: HowToAnalyze) -> Msg: """消去法(メモ削除)メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 Returns: Msg: メッセージ """ return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_ELIMIONATION).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), region=SudokuUtil.cnv_region_to_text(how_anlz.region), triggerSqu=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[0]), removeMemo=how_anlz.remove_memo_list[0]))
def how_to_simple_chain(cls, how_anlz: HowToAnalyze) -> Msg: """シンプルチェーン法メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 Returns: Msg: メッセージ """ return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_SIMPLE_CHAIN).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), chainSquList=SudokuUtil.cnv_squ_list_to_text( how_anlz.chain_squ_list), removeMemo=how_anlz.remove_memo_list[0]))
def how_to_locked_candidates(cls, how_anlz: HowToAnalyze) -> Msg: """ロックされた候補法メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 Returns: Msg: メッセージ """ return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_LOCKED_CANDIDATES).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), triggerSqu=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[0]), removeMemo=how_anlz.remove_memo_list[0], regionPos=how_anlz.trigger_squ_list[0].row if how_anlz.region == Region.ROW else how_anlz.trigger_squ_list[0].clm, region=SudokuUtil.cnv_region_to_text(how_anlz.region)))
def initBeforeAnalyze(wk: AnalyzeWk) -> None: """解析前初期設定 Args: wk (AnalyzeWk): ワーク """ # ヒントまたは値がない枡にメモ値を設定 for squ in SudokuUtil.find_unfixed_squ_from_flame(wk.flame): if (squ.get_fixed_val() is None): if len(squ.memo_val_list) == 0: squ.memo_val_list.extend([1, 2, 3, 4, 5, 6, 7, 8, 9])
def how_to_hidden_pair(cls, how_anlz: HowToAnalyze, pair_list: List[int]) -> Msg: """隠れペア法メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 pair_list (List[int]): ペアリスト Returns: Msg: メッセージ """ return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_HIDDEN_PAIR).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), region=SudokuUtil.cnv_region_to_text(how_anlz.region), triggerSquList=SudokuUtil.cnv_squ_list_to_text( how_anlz.trigger_squ_list), pairList=SudokuUtil.cnv_memo_list_to_text(pair_list), removeMemo=how_anlz.remove_memo_list[0]))
def _removeMemo( how_anlz_list: List[HowToAnalyze], region: Region, squ_list: List[Square] ) -> None: """メモの除外 ヒント(値)によってメモを除外 Args: how_anlz_list (List[ChangeHistroy]): 解析方法 region (Region): 領域 squ_list (List[Square]): 枡リスト """ # 確定枡を抽出 not_none_squ_list: List[Square] = SudokuUtil.find_fixed_squ_from_region( squ_list) # 未確定枡を抽出 none_squ_list: List[Square] = SudokuUtil.find_unfixed_squ_from_region( squ_list) # メモからヒント(値)を除外 for none_squ in none_squ_list: for memo in none_squ.memo_val_list[:]: for not_none_squ in not_none_squ_list: if memo == not_none_squ.get_fixed_val(): none_squ.memo_val_list.remove(memo) # 解析方法生成 how_anlz: HowToAnalyze = HowToAnalyze( Method.ELIMIONATION) how_anlz.region = region how_anlz.remove_memo_list.append(memo) how_anlz.changed_squ = none_squ how_anlz.trigger_squ_list.append(not_none_squ) how_anlz.msg = MsgFactory.how_to_elimionation(how_anlz) how_anlz_list.append(how_anlz)
def not_exist_num_area(cls, num: int, squ: Square) -> Msg: """エリアに数字がないメッセージ生成 Args: num (int): 数字 squ (Square): 枡 Returns: Msg: メッセージ """ return Msg( MsgType.ERROR, cls._get_msg(MsgCode.NOT_EXIST_NUM_AREA).format( num=num, squ=SudokuUtil.cnv_squ_to_text(squ)))
def howto_summary(cls, idx: int, method: Method, cnt: int) -> Msg: """解析方法サマリメッセージ生成 Args: cnt (int): 確定枡数 Returns: Msg: メッセージ """ return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_SUMMARY).format( idx=idx, method=SudokuUtil.cnv_method_to_text(method), cnt=cnt))
def how_to_x_wing(cls, how_anlz: HowToAnalyze, region_pair: Tuple[int, int]) -> Msg: """X-Wing法メッセージ生成 Args: how_anlz (HowToAnalyze): 解析方法 region_pair (Tuple[int, int]): 方向位置 Returns: Msg: メッセージ """ # regionPos1、regionPos2を算出 pos_set: Set[int] = set() for squ in how_anlz.trigger_squ_list: if how_anlz.region == Region.ROW: pos_set.add(squ.row) else: pos_set.add(squ.clm) pos_list: List[int] = list(pos_set) pos_list.sort() return Msg( MsgType.INFO, cls._get_msg(MsgCode.HOW_TO_X_WING).format( changedSqu=SudokuUtil.cnv_squ_to_text(how_anlz.changed_squ), removeMemo=how_anlz.remove_memo_list[0], regionPos1=pos_list[0], regionPos2=pos_list[1], region=SudokuUtil.cnv_region_to_text(how_anlz.region), triggerSqu1=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[0]), triggerSqu2=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[1]), triggerSqu3=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[2]), triggerSqu4=SudokuUtil.cnv_squ_to_text( how_anlz.trigger_squ_list[3])))
def _analyze_naked_pair( wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze], region: Region, squ_list: List[Square] ) -> None: """ネイキッドペア解析 Args: wk (AnalyzeWk): ワーク how_anlz_list (List[HowToAnalyze]): 解析方法 region (Region): 領域 """ # 未確定枡を取得 unfixed_list: List[Square] = SudokuUtil.find_unfixed_squ_from_region( squ_list) # 対象領域の全ての枡が確定している if len(unfixed_list) == 0: return # ペア数(2~8)を大きくしながら解析 # ※制限時は2~3 find_pair_cnt: int = 8 if Method.NAKED_PAIR in wk.limit_method_list: find_pair_cnt = 3 for naked_num in range(2, find_pair_cnt + 1): # 未確定枡数-1よりネイキッド数の方が大きくなったら処理終了 # [補足] # 未確定数とネイキッド数が同じ場合、必ず全ての枡がペアになるため除外するメモがなくなる # ⇒-1して終了条件の比較を行っている if len(unfixed_list) - 1 <= naked_num: return # ペア可能リストを算出 can_pair_list = list() for unfixed_squ in unfixed_list: if len(unfixed_squ.memo_val_list) > naked_num: continue # naked_num=3の場合 # +[1]--------------------------------------+ # | 1:1 1:2 1:3 | # | m=[N,M,O](@) m=[N,P](*) ? | # | 2:1 2:2 2:3 | # | m=[M,O]($) m=[M,Q](*) ? | # | 3:1 3:2 3:3 | # | m=[N,M](#) m=[O,R](*) m=[N,M,O,R,Q,S] | # +[4]--------------------------------------+ # @枡、$枡、#枡、*枡3個を抽出 # 1:1 m=[N,M,O](@) # 1:2 m=[N,P](*) # 2:1 m=[M,O]($) # 2:2 m=[M,Q](*) # 3:1 m=[N,M](#) # 3:2 m=[O,R](*) can_pair_list.append(unfixed_squ) # ペア可能リストがペア数より小さい場合は対象外 # 次のペア数を調べる if len(can_pair_list) < naked_num: continue # ペアを見つける # 例に当てはめると # 1:1 m=[N,M,O](@) # 1:2 m=[N,P](*) # 2:1 m=[M,O]($) # 2:2 m=[M,Q](*) # 3:1 m=[N,M](#) # 3:2 m=[O,R](*) # から # 1:1 m=[N,M,O](@) # 2:1 m=[M,O]($) # 3:1 m=[N,M](#) # を見つける for pivot_squ in can_pair_list[:]: pivot_pair: Set[int] = set(pivot_squ.memo_val_list) for compare_squ in can_pair_list[:]: if pivot_squ == compare_squ: continue pivot_pair = pivot_pair.union(set(compare_squ.memo_val_list)) if len(pivot_pair) > naked_num: break if len(pivot_pair) > naked_num: can_pair_list.remove(pivot_squ) # ネイキッド数≒枡数は対象外 if len(can_pair_list) != naked_num: continue # ペア発見! pair_set: Set[int] = set() for squ in can_pair_list: pair_set = pair_set.union(squ.memo_val_list) pair_list: List[int] = list(pair_set) pair_list.sort() # 変更枡抽出 change_squ_list: List[Square] = list() for squ in unfixed_list: if squ in can_pair_list: continue for memo in squ.memo_val_list: if memo in pair_list: change_squ_list.append(squ) break # ペアは見つかったが、変更枡が存在しない if len(change_squ_list) == 0: continue for change_squ in change_squ_list: for loop_memo in pair_list: if loop_memo not in change_squ.memo_val_list: continue # メモを除外 change_squ.memo_val_list.remove(loop_memo) # 解析方法生成 how_anlz: HowToAnalyze = HowToAnalyze( Method.NAKED_PAIR) how_anlz.region = region how_anlz.changed_squ = change_squ how_anlz.remove_memo_list.append(loop_memo) how_anlz.trigger_squ_list.extend(can_pair_list) how_anlz.msg = MsgFactory.how_to_naked_pair( how_anlz, pair_list) how_anlz_list.append(how_anlz)
def creata_json_response( result: bool, wk: AnalyzeWk ) -> JsonResponse: """JSONレスポンス生成 Args: result (bool): 解析結果 wk (AnalyzeWk): ワーク Returns: JsonResponse: JSONレスポンス """ # 解析履歴変換 history_json_list: List[Dict[str, any]] = list() for histroy in wk.histroy_list: # wk_flame, wk_change_history_list how_anlz_json_list: List[Dict] = list() if histroy.how_anlz_list is not None: for how_anlz in histroy.how_anlz_list: # how_anlz_json_list: List[Dict] = list() how_anlz_json_list.append(how_anlz.cnv_to_json()) history_json: Dict[str, any] = dict() history_json["flame"] = histroy.flame.cnv_to_json() history_json["howToAnalyzeList"] = how_anlz_json_list history_json_list.append(history_json) msg_json_list: List[Dict] = list() # 解析結果サマリー for idx, history in enumerate(wk.histroy_list): # 解析方法なしははサマリーしようがないためスキップ if history.how_anlz_list is None or len(history.how_anlz_list) == 0: continue method: Method = history.how_anlz_list[0].method # 解析開始はサマリーしようがないためスキップ # エラーは後続の処理で出力しているためスキップ if method == Method.START or method == Method.ERROR_CHECK: continue # 例>N国同盟によってN回枡が更新されました。 msg_json_list.append( MsgFactory.howto_summary( idx + 1, method, len(history.how_anlz_list)) .cnv_to_json()) # エラー抽出 for wk_msg in wk.msg_list: msg_json_list.append(wk_msg.cnv_to_json()) # N個の枡の答えが判明しました。 fixed_num: int = \ len(SudokuUtil.find_fixed_squ_from_flame(wk.flame))\ - len(wk.hint_list) if fixed_num > 0: msg_json_list.append( MsgFactory.fixed_num(fixed_num) .cnv_to_json()) # N個の枡の答えが判明出来ませんでした。 unfixed_num: int = len(SudokuUtil.find_unfixed_squ_from_flame(wk.flame)) if unfixed_num > 0: msg_json_list.append( MsgFactory.unfixed_num(unfixed_num) .cnv_to_json()) return JsonResponse({ "result": result, "msgList": msg_json_list, "historyList": history_json_list })
def analyze(wk: AnalyzeWk) -> bool: """解析 Args: wk (AnalyzeWk): 数独WK Returns: bool: 解析成功時にTrue """ # 初回エラーチェック how_anlz_list_err: List[HowToAnalyze] = simpleErrorCheck.errorCheck( wk, first_check=True) if len(how_anlz_list_err) > 0: wk.addHistryForErr(how_anlz_list_err) return False # 解析前初期化 initBeforeAnalyze(wk) # 解法リスト analyze_method_list: List[ Tuple[ Method, Callable[[AnalyzeWk, List[HowToAnalyze], bool]] ] ] = [] # 消去法 analyze_method_list.append( (Method.ELIMIONATION, methodElimionationRemoveMemo.analyze)) # 消去法one memo analyze_method_list.append( (Method.ELIMIONATION_ONE_MEMO, methodElimionationOneMemo.analyze)) # 消去法only memo analyze_method_list.append( (Method.ELIMIONATION_ONE_MEMO, methodElimionationOnlyMemo.analyze)) # ロックされた候補法 if Method.LOCKED_CANDIDATES in wk.use_method_list: analyze_method_list.append( (Method.LOCKED_CANDIDATES, methodLockedCandidates.analyze)) # ネイキッドペア法 if Method.NAKED_PAIR in wk.use_method_list: analyze_method_list.append( (Method.NAKED_PAIR, methodNakedPair.analyze)) # 隠れペア法 if Method.HIDDEN_PAIR in wk.use_method_list: analyze_method_list.append( (Method.NAKED_PAIR, methodHiddenPair.analyze)) # X-Wing if Method.X_WING in wk.use_method_list: analyze_method_list.append( (Method.X_WING, methodXWing.analyze)) # XYチェーン法 if Method.XY_CHAIN in wk.use_method_list: analyze_method_list.append( (Method.XY_CHAIN, methodXYChain.analyze)) # シンプルチェーン法 if Method.SIMPLE_CHAIN in wk.use_method_list: analyze_method_list.append( (Method.SIMPLE_CHAIN, methodSimpleChain.analyze)) # 解析メインループ while True: # 未確定枡がなくなったら処理終了 if len(SudokuUtil.find_unfixed_squ_from_flame(wk.flame)) == 0: return True how_anlz_list: List[HowToAnalyze] = list() # 解法リストループ for method, analyze_func in analyze_method_list: # 解析 if not analyze_func(wk, how_anlz_list): wk.addHistryForErr(how_anlz_list) return False # 解析結果がない場合は次の解法で解析する if len(how_anlz_list) == 0: continue wk.addHistry(how_anlz_list) # エラーチェック how_anlz_list_err: List[HowToAnalyze] =\ simpleErrorCheck.errorCheck(wk) if len(how_anlz_list_err) > 0: wk.addHistryForErr(how_anlz_list_err) return False # 解法リストループからbreakし、 # 最初の解法(消去法)から解析し直す break # 解析結果がある場合は最初(消去法)から解析し直す if len(how_anlz_list) != 0: continue # 解析結果なし break return True
def _find_x_wing(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze], region: Region) -> None: """X-Wing法 Args: wk (AnalyzeWk): ワーク how_anlz_list (List[HowToAnalyze]): 解析方法リスト region (Region): 領域 """ # 行辞書(または列辞書) region_dict: Dict[int, List] if region == Region.ROW: region_dict = wk.row_dict else: region_dict = wk.clm_dict for loop_memo in range(1, 10): # X-Wingの対象 # メモが2個の行(列)を抽出 two_memo_list: List[List[Square]] = list() for squ_list in region_dict.values(): # 行(列)内のメモを含む枡リストを取得 # +[1]-------------------------+[2]-------------------------+[3]-------------------------+ # | 1:1 1:2 1:3 | 1:4 1:5 1:6(*) | 1:7 1:8 1:9(*) | # | ? ? ? | m=[N,?] ? m=[N,?] | m=[N,?] ? m=[N,?] | <-この行の枡を取得、ただし枡数が2でないので対象外 # | 2:1 2:2 2:3 | 2:4 2:5 2:6 | 2:7 2:8 2:9 | # | ? ? val=N | ? ? ? | ? ? ? | # | 3:1 3:2 3:3 | 3:4 3:5 3:6(@) | 3:7 3:8 3:9($) | # | ? ? ? | ? ? m=[N,?] | ? ? m=[N,?] | <-この行の枡を取得 # +[4]-------------------------+[5]-------------------------+[6]-------------------------+ # | 4:1 4:2 4:3 | 4:4 4:5 4:6(*) | 4:7 4:8 4:9(*) | # | ? ? ? | m=[N,?] ? m=[N,?] | m=[N,?] ? m=[N,?] | <-この行の枡を取得、ただし枡数が2でないので対象外 # | 5:1 5:2 5:3 | 5:4 5:5 5:6($) | 5:7 5:8 5:9(@) | # | ? ? ? | ? ? m=[N,?]$ | ? ? m=[N,?]@ | <-この行の枡を取得 # | 6:1 6:2 6:3 | 6:4 6:5 6:6 | 6:7 6:8 6:9 | # | ? hint=N ? | ? ? ? | ? ? ? | # +[7]-------------------------+[8]-------------------------+[9]-------------------------+ # | 7:1 7:2 7:3 | 7:4 7:5 7:6 | 7:7 7:8 7:9 | # | m=[N,?] ? ? | ? m=[N,?] ? | ? ? ? | <-この行の枡を取得 # | 8:1 8:2 8:3 | 8:4 8:5 8:6 | 8:7 8:8 8:9 | # | m=[N,?] ? ? | ? m=[N,?] ? | ? ? ? | <-この行の枡を取得 # | 9:1 9:2 9:3 | 9:4 9:5 9:6 | 9:7 9:8 9:9 | # | ? ? ? | ? ? ? | ? hint=N ? | # +----------------------------+----------------------------+----------------------------+ # loop_memoを含む枡を抽出し、そのメモが2個かどうかを判定 include_list: List[ Square] = SudokuUtil.find_squ_include_memo_from_region( squ_list, loop_memo) # 行(列)内でメモが2個でないとX-Wingが成立しない if len(include_list) != 2: continue two_memo_list.append(include_list) # 発見出来た行(列)が2以下だとそもそもX-Wing対象にならない if len(two_memo_list) < 2: continue # 同一列(行)でグルーピング # 以下のような辞書を生成 # キー:(列、列) # 値:[[枡、枡],[枡、枡]] region_grouping_dict: Dict[Tuple[int, int], List[List[Square]]] = dict() for include_list in two_memo_list: # キー region_pair: Tuple[int, int] if region == Region.ROW: # 行の場合は列でグルーピング region_pair = (include_list[0].clm, include_list[1].clm) else: # 列の場合は行でグルーピング region_pair = (include_list[0].row, include_list[1].row) # 値 if region_pair not in region_grouping_dict: region_grouping_dict[region_pair] = list() region_list: List[List[Square]] = region_grouping_dict[region_pair] region_list.append(include_list) # グルーピング後、対象行(列)が2つでなければX-Wingの対象外 for region_pair in list(region_grouping_dict.keys()): region_list = region_grouping_dict[region_pair] if len(region_list) != 2: region_grouping_dict.pop(region_pair) # 対象なし if len(region_grouping_dict) == 0: continue # 除外するメモの検索対象辞書 # 行の場合は列から探し、列の場合は行から探す target_dict: Dict[int, List[Square]] if region == Region.ROW: target_dict = wk.clm_dict else: target_dict = wk.row_dict # 除去するメモを抽出 for region_pair, region_list in region_grouping_dict.items(): # X-Wingで見つかった枡 xwing_squ_list: List[Square] = list() for include_list in region_list: for squ in include_list: xwing_squ_list.append(squ) for region_pos in region_pair: # 同一列(行)からメモを含む枡を取得 # (除外対象枡) change_list = SudokuUtil.find_squ_include_memo_from_region( target_dict[region_pos], loop_memo) for change_squ in change_list: # 同一枡は消してはいけない if change_squ in xwing_squ_list: continue # メモを除外 change_squ.memo_val_list.remove(loop_memo) # 解析方法生成 how_anlz: HowToAnalyze = HowToAnalyze(Method.X_WING) how_anlz.region = region how_anlz.changed_squ = change_squ how_anlz.remove_memo_list.append(loop_memo) how_anlz.trigger_squ_list.extend(xwing_squ_list) how_anlz.msg = MsgFactory.how_to_x_wing( how_anlz, region_pair) how_anlz_list.append(how_anlz) if len(how_anlz_list) > 0: return
def _find_share_squ(wk: AnalyzeWk, squ1: Square, squ2: Square) -> List[Square]: """2個の枡の共通枡を検索 Args: wk (AnalyzeWk): 解析WK squ1 (Square): 枡1 squ2 (Square): 枡2 Returns: List[Square]: 共通枡 """ share_list: List[Square] = list() region_area_level: Region = _which_region_area_level(squ1, squ2) # エリアレベルで枡1、枡2が同一行、同一列に存在する場合 if region_area_level == Region.ROW or\ region_area_level == Region.CLM: region_pos_squ1: int region_pos_squ2: int if region_area_level == Region.ROW: region_pos_squ1 = squ1.row region_pos_squ2 = squ2.row else: region_pos_squ1 = squ1.clm region_pos_squ2 = squ2.clm # 枡1と枡2が同一行(列) if region_pos_squ1 == region_pos_squ2: # +[1]-------------------+[2]-------------------+[3]-------------------+ # | 1:1(1) 1:2(*) 1:3(*) | 1:4(2) 1:5(*) 1:6(*) | 1:7(*) 1:8(*) 1:9(*) | # *枡が共通枡になる region_squ_list: List[Square] if region_area_level == Region.ROW: region_squ_list = wk.row_dict[region_pos_squ1] else: region_squ_list = wk.clm_dict[region_pos_squ1] for region_squ in region_squ_list: if region_squ == squ1 or region_squ == squ2: continue share_list.append(region_squ) # 枡1と枡2が別行(列) else: # +[1]-------------------+[2]-------------------+[3]-------------------+ # | 1:1(1) 1:2 1:3 | 1:4 1:5 1:6 | 1:7(*) 1:8(*) 1:9(*) | # | 2:1 2:2 2:3 | 2:4 2:5 2:6 | 2:7 2:8 2:9 | # | 3:1(*) 3:2(*) 3:3(*) | 3:4 3:5 3:6 | 3:7(2) 3:8 3:9 | # *枡が共通枡になる # 枡1と同一行(列)にある枡を取得 # +[1]-------------------+[2]-------------------+[3]-------------------+ # | 1:1(1) 1:2 1:3 | 1:4 1:5 1:6 | 1:7(*) 1:8(*) 1:9(*) | # ^^^^^^ ^^^^^^ ^^^^^^ region_squ_list1: List[Square] if region_area_level == Region.ROW: region_squ_list1 = wk.row_dict[squ1.row] else: region_squ_list1 = wk.clm_dict[squ1.clm] for region_squ in region_squ_list1: # 枡2と同一エリアにある枡を対象に if region_squ == squ1 or\ region_squ.area_id != squ2.area_id: continue share_list.append(region_squ) # 枡2と同一行(列)にある枡を取得 # | 3:1(*) 3:2(*) 3:3(*) | 3:4 3:5 3:6 | 3:7(2) 3:8 3:9 | # ^^^^^^ ^^^^^^ ^^^^^^ region_squ_list2: List[Square] if region_area_level == Region.ROW: region_squ_list2 = wk.row_dict[squ2.row] else: region_squ_list2 = wk.clm_dict[squ2.clm] for region_squ in region_squ_list2: # 枡1と同一エリアにある枡を対象に if region_squ == squ2 or\ region_squ.area_id != squ1.area_id: continue share_list.append(region_squ) # エリアレベルで枡1、枡2が同一行、同一列に存在しない場合 else: # 交差枡を算出する share_list = SudokuUtil.find_cross_squ(wk, squ1, squ2) return share_list
def analyze(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze]) -> bool: """シンプルチェーン 1つの数字に注目して 枡 =強= 枡 =弱= 枡 =強= 枡 =弱= 枡 =強= 枡 のように強リンクではじまって弱リンク、強リンク...の組み合わせが続き、 強リンクで終わるパターンで最初と最後の交差枡にはその数字は入らないという解法 ※弱リンクの部分は強リンクになっても可 ※強リンク、弱リンクの解説はLinkType.pyのdocstringを参照 例> +[1]-------------------------+[2]-------------------------+[3]-------------------------+ | 1:1 1:2 1:3 | 1:4 1:5 1:6 | 1:7 1:8 1:9 | | ? ? ? | ? ? ? | ? ? hint=N | | 2:1 2:2 2:3 | 2:4 2:5 2:6 | 2:7 2:8 2:9 | | ? ? ? | ? ? hint=N | ? ? ? | | 3:1 3:2 3:3 | 3:4 3:5 3:6 | 3:7 3:8 3:9 | | hint=N ? ? | ? ? ? | ? ? ? | +[4]-------------------------+[5]-------------------------+[6]-------------------------+ | 4:1 4:2 4:3 | 4:4 4:5(#) 4:6 | 4:7 4:8 4:9 | | ? ? m=[N,?] | ? m=[N,?] ? | ? ? ? | | 5:1 5:2 5:3 | 5:4 5:5 5:6 | 5:7 5:8 5:9 | | ? ? ? | ? ? ? | hint=N ? ? | | 6:1 6:2(*) 6:3 | 6:4(@) 6:5 6:6 | 6:7 6:8 6:9 | | ? m=[N,?] m=[N,?] | m=[N,?] ? ? | ? ? ? | +[7]-------------------------+[8]-------------------------+[9]-------------------------+ | 7:1 7:2 7:3 | 7:4 7:5 7:6 | 7:7 7:8 7:9 | | ? m=[N,?] m=[N,?] | ? ? ? | ? ? ? | | 8:1 8:2 8:3 | 8:4 8:5 8:6 | 8:7 8:8 8:9 | | ? ? ? | m=[N,?] m=[N,?] ? | ? hint=N ? | | 9:1 9:2(+) 9:3 | 9:4 9:5(!) 9:6 | 9:7 9:8 9:9 | | ? m=[N,?] ? | ? m=[N,?] ? | ? ? ? | +----------------------------+----------------------------+----------------------------+ ※8:8がNで確定している、上記のような状態の枠は本処理には来ないが、、、説明用に目をつぶる 数字Nで以下のチェーンが形成されている @枡 =強= #枡 =弱= !枡 =強= +枡 チェーンの最初の@枡と最後の+枡の交差枡(*枡)からNを除外することが出来る。 検証> 上記例に当てはめて交差枡*がNで確定すると仮定 ・@枡からNが除外される @枡 =強= #枡 =弱= !枡 =強= +枡 ^^^ ・#枡がNで確定する @枡 =強= #枡 =弱= !枡 =強= +枡 ^^^ ・!枡からNが除外される @枡 =強= #枡 =弱= !枡 =強= +枡 ^^^ ・+枡がNで確定する @枡 =強= #枡 =弱= !枡 =強= +枡 ^^^ +[1]-------------------------+[2]-------------------------+[3]-------------------------+ | 1:1 1:2 1:3 | 1:4 1:5 1:6 | 1:7 1:8 1:9 | | ? ? ? | ? ? ? | ? ? hint=N | | 2:1 2:2 2:3 | 2:4 2:5 2:6 | 2:7 2:8 2:9 | | ? ? ? | ? ? hint=N | ? ? ? | | 3:1 3:2 3:3 | 3:4 3:5 3:6 | 3:7 3:8 3:9 | | hint=N ? ? | ? ? ? | ? ? ? | +[4]-------------------------+[5]-------------------------+[6]-------------------------+ | 4:1 4:2 4:3 | 4:4 4:5(#) 4:6 | 4:7 4:8 4:9 | | ? ? m=[?] | ? v=N ? | ? ? ? | | 5:1 5:2 5:3 | 5:4 5:5 5:6 | 5:7 5:8 5:9 | | ? ? ? | ? ? ? | hint=N ? ? | | 6:1 6:2(*) 6:3 | 6:4(@) 6:5 6:6 | 6:7 6:8 6:9 | | ? v=N m=[?] | m=[?] ? ? | ? ? ? | +[7]-------------------------+[8]-------------------------+[9]-------------------------+ | 7:1 7:2 7:3 | 7:4 7:5 7:6 | 7:7 7:8 7:9 | | ? m=[?] m=[N,?] | ? ? ? | ? ? ? | | 8:1 8:2 8:3 | 8:4 8:5 8:6 | 8:7 8:8 8:9 | | ? ? ? | m=[N,?] m=[?] ? | ? hint=N ? | | 9:1 9:2(+) 9:3 | 9:4 9:5(!) 9:6 | 9:7 9:8 9:9 | | ? v=N ? | ? m=[?] ? | ? ? ? | +----------------------------+----------------------------+----------------------------+ 2列目でNが2つ出てくるため矛盾が生じる。 Args: wk (AnalyzeWk): ワーク how_anlz_list (List[HowToAnalyze]): 解析方法 Returns: bool: エラーの場合にFalse """ for loop_memo in range(1, 10): # チェーンを作成 all_chain_list: List[List[Chain]] =\ _create_simple_chain(wk, loop_memo) for chain_list in all_chain_list: first_squ = chain_list[0].squ last_squ = chain_list[len(chain_list) - 1].squ # 交差枡取得 cross_squ_list: List[Square] = SudokuUtil.find_cross_squ( wk, first_squ, last_squ) # 交差枡が変更対象かどうか判定 change_squ_list: List[Square] = list() for cross_squ in cross_squ_list: # 未確定かつメモが存在する枡が対象となる if cross_squ.get_fixed_val() is None and\ loop_memo in cross_squ.memo_val_list: change_squ_list.append(cross_squ) # 対象なし # ⇒次のチェーンをチェック if len(change_squ_list) == 0: continue # 対象あり chain_squ_list: List[Square] = list() for chain in chain_list: chain_squ_list.append(chain.squ) for change_squ in change_squ_list: # メモを除外 change_squ.memo_val_list.remove(loop_memo) # 解析方法生成 how_anlz: HowToAnalyze = HowToAnalyze(Method.SIMPLE_CHAIN) how_anlz.changed_squ = change_squ how_anlz.remove_memo_list.append(loop_memo) how_anlz.trigger_squ_list.extend(chain_squ_list) how_anlz.chain_squ_list.extend(chain_squ_list) how_anlz.msg = MsgFactory.how_to_simple_chain(how_anlz) how_anlz_list.append(how_anlz) return True return True