def analyze(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze]) -> bool:
    """消去法

    ある枡にメモが一つしかなければその枡の値が確定できる

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法

    Returns:
        bool: エラーの場合にFalse
    """

    # メモ値がひとつしかない=そこの枡にはそれしか入らない
    for squ in wk.all_squ_list:
        if len(squ.memo_val_list) == 1:
            squ.val = squ.memo_val_list[0]
            squ.memo_val_list.clear()

            # 解析方法生成
            how_anlz: HowToAnalyze = HowToAnalyze(Method.ELIMIONATION_ONE_MEMO)
            how_anlz.commit_val = squ.val
            how_anlz.changed_squ = squ
            how_anlz.msg = MsgFactory.how_to_elimionation_one_memo(how_anlz)

            how_anlz_list.append(how_anlz)

    return True
示例#2
0
    def __post_init__(self) -> None:
        """コンストラクタの後に呼ばれるメソッド
        """

        from sudokuapp.util.MsgFactory import MsgFactory

        # 画面から来た状態を保持するため、解析履歴に投入
        start_howto: HowToAnalyze = HowToAnalyze(Method.START)
        start_howto.msg = MsgFactory.start_analyze()
        self.histroy_list.append(History(self.flame.clone(), [start_howto]))
        for area in self.flame.area_list:
            for squ in area.squ_list:
                # 全枡リスト
                self.all_squ_list.append(squ)

                # 全枡辞書
                self._all_squ_dict[self._create_key_for_squ_dict(
                    squ.row, squ.clm)] = squ

                # ヒント枡
                if squ.hint_val is not None:
                    self.hint_list.append(squ)

                # 行辞書
                if squ.row not in self.row_dict:
                    self.row_dict[squ.row] = list()
                self.row_dict[squ.row].append(squ)

                # 列辞書
                if squ.clm not in self.clm_dict:
                    self.clm_dict[squ.clm] = list()
                self.clm_dict[squ.clm].append(squ)
示例#3
0
    def addHistryForErr(self, how_anlz_list_err: List[HowToAnalyze]) -> None:
        """枠をヒストリーに追加

        エラー用

        Args:
            how_anlz_list (List[HowToAnalyze]): 解析方法

        """

        from sudokuapp.util.MsgFactory import MsgFactory

        how_to_list: List[HowToAnalyze] = list()
        how_to_title: HowToAnalyze = HowToAnalyze(Method.ERROR_CHECK)
        how_to_title.msg = MsgFactory.error_info(len(how_anlz_list_err))
        how_to_list.append(how_to_title)
        how_to_list.extend(how_anlz_list_err)

        self.histroy_list.append(History(self.flame.clone(), how_to_list))
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)
示例#5
0
def _onlyMemo(how_anlz_list: List[HowToAnalyze], region: Region,
              squ_list: List[Square]) -> bool:
    """エリア内(行、列)にメモが一つしかない

    ⇒メモで値を確定させる

    Args:
        how_anlz_list (List[HowToAnalyze]): 解析方法
        from_type (Region): 領域
        squ_list (List[Square]): 枡リスト

    Returns:
        bool: エラーの場合にFalse

    """

    wk_memo_dict: Dict[str, List[Square]] = dict()
    # 下記のようなDICTを作成
    # 枡A memo=[1 2 3]
    # 枡B memo=[1 2]
    # 枡C memo=[1 2 3 4]
    # ⇒
    # 1: [枡A 枡B 枡C]
    # 2: [枡A 枡B 枡C]
    # 3: [枡A 枡B]
    # 4: [枡C]
    for squ in squ_list:
        if (squ.get_fixed_val() is not None):
            continue
        for memo in squ.memo_val_list:
            if memo not in wk_memo_dict:
                wk_memo_dict[memo] = list()
            wk_memo_dict[memo].append(squ)

    # 複数のメモ値が入るメモを除外
    # 1: [枡A 枡B 枡C] ←除外
    # 2: [枡A 枡B 枡C] ←除外
    # 3: [枡A 枡B] ←除外
    # 4: [枡C]
    for loop_memo in list(wk_memo_dict.keys()):
        if len(wk_memo_dict[loop_memo]) != 1:
            del wk_memo_dict[loop_memo]

    # エラーチェック
    # 下記のような場合はNG
    # 枡A memo=[1 2]
    # 枡B memo=[1 2]
    # 枡C memo=[3 4]
    # ⇒
    # 1: [枡A 枡B] ←除外
    # 2: [枡A 枡B] ←除外
    # 3: [枡C] ←3は枡Cにしか入らないが、、、
    # 4: [枡C] ←4も枡Cにしか入らない。。。
    #           ⇒例えば3で枡Cを確定してしまうと
    #             4が入る枡がなくなってしまう
    # for memo, wk_squ_list in wk_memo_dict.items():
    #     if len(wk_squ_list) != 1:
    #         continue
    #     pass

    for memo, wk_squ_list in wk_memo_dict.items():
        squ: Square = wk_squ_list[0]
        squ.val = memo
        squ.memo_val_list.clear()

        # 解析方法生成
        how_anlz: HowToAnalyze = HowToAnalyze(Method.ELIMIONATION_ONLY_MEMO)
        how_anlz.region = region
        how_anlz.commit_val = squ.val
        how_anlz.changed_squ = squ
        how_anlz.msg = MsgFactory.how_to_elimionation_only_memo(how_anlz)
        how_anlz_list.append(how_anlz)

    return True
def analyze(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze]) -> bool:
    """XYチェーン

    メモが2つしかない枡が共通の数字を介してチェーンを形成する場合、共通枡からメモを除外する解法

    例>
    +[1]-------------------------+[2]-------------------------+ ...
    | 1:1(@)   1:2      1:3      | 1:4      1:5(?)   1:6      | ...
    | m=[N,M]  ?        ?        | ?        m=[?,N]  ?        | ...
    | 2:1      2:2      2:3      | 2:4      2:5      2:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    | 3:1      3:2      3:3      | 3:4      3:5      3:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    +[4]-------------------------+[5]-------------------------+ ...
    | 4:1      4:2      4:3      | 4:4      4:5      4:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    | 5:1      5:2      5:3      | 5:4      5:5      5:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    | 6:1      6:2      6:3      | 6:4      6:5      6:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    +[7]-------------------------+[8]-------------------------+ ...
    | 7:1      7:2      7:3($)   | 7:4      7:5(+)   7:6      | ...
    | ?        ?        m=[O,P]  | ?        m=[N,P]  ?        | ...
    | 8:1      8:2      8:3      | 8:4      8:5      8:6      | ...
    | ?        ?        ?        | ?        ?        ?        | ...
    | 9:1(#)   9:2      9:3      | 9:4      9:5      9:6      | ...
    | m=[M,O]  ?        ?        | ?        ?        ?        | ...
    +----------------------------+----------------------------+ ...

    上記例に当てはめて考えると以下のようなチェーンが形成される場合
    @(m=[N,M]) =M= #(m=[M,O]) =O= $(m=[O,P]) =P= +(m=[N,P])
    チェーンの始端と終端の交差枡?のメモからNを除外することができる。

    検証>
    交差枡(+)がNだと仮定しすると
    @=M
    #=O
    $=P
    +=N
    となり5列目にNが2つ出現し矛盾が発生する。
    上記は@枡から検証したが、+枡がPから始まると仮定した場合でも矛盾が発生する。
    ⇒
    この事からXYチェーンの始端と終端の交差枡には共通のメモNがあると矛盾が存在するため
    メモNを除外できる

    XYチェーンの条件
    ・3つ以上の枡でチェーンが形成されていること
    ・始端と終端に同じメモ(以降共通メモと称す)が存在すること
    ・始端の次の枡が共通メモでリンクされていないこと
    ・終端の前の枡が共通メモでリンクされていないこと

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法

    Returns:
        bool: エラーの場合にFalse
    """

    # XYチェーン作成
    all_chain_list: List[List[Chain]] = _create_xy_chain(wk)

    for chain_list in all_chain_list:
        first_squ = chain_list[0].squ
        last_squ = chain_list[len(chain_list) - 1].squ

        # 共通するメモを抽出
        dup_memo_list = _get_dup_memo_list(first_squ, last_squ)

        # 共通枡を算出
        share_list: List[Square] = _find_share_squ(wk, first_squ, last_squ)

        change_squ_memo_dict: Dict[int, List[Square]] = dict()
        for share_squ in share_list:
            if share_squ.get_fixed_val() is not None:
                continue

            # 共通枡がチェーンリストに含まれる場合は対象外
            # TODO: こんなパターンないはず?
            #       共通するメモはチェーンの中に入ってこないはずだから下記のパターンは存在しない?
            #       後日考える
            chain_include = False
            for chain in chain_list:
                if share_squ == chain.squ:
                    chain_include = True
                    break
            if chain_include:
                continue

            # 共通枡にメモが存在する?
            for loop_memo in dup_memo_list:
                if loop_memo not in share_squ.memo_val_list:
                    continue
                change_squ_list: List[Square]
                if loop_memo in change_squ_memo_dict:
                    change_squ_list = change_squ_memo_dict[loop_memo]
                else:
                    change_squ_list = list()
                    change_squ_memo_dict[loop_memo] = change_squ_list
                change_squ_list.append(share_squ)

        # 対象なし
        if len(change_squ_memo_dict) == 0:
            # 次のチェーンリストを検索
            continue

        # TODO: debug
        print("ヒットで")
        print("  chain_list={}".format(chain_list))
        print("  share_list={}".format(share_list))

        # 対象あり
        for loop_memo, change_squ_list in change_squ_memo_dict.items():
            print("  ゲスよ loop_memo={} change_squ_list={}".format(
                loop_memo, change_squ_list))

            chain_squ_list: List[Square] = list()
            for chain in chain_list:
                chain_squ_list.append(chain.squ)

            for change_squ in change_squ_list:

                print("    change_squ={}".format(change_squ))

                # メモを除外
                change_squ.memo_val_list.remove(loop_memo)

                # 解析方法生成
                how_anlz: HowToAnalyze = HowToAnalyze(Method.XY_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_xy_chain(how_anlz)

                how_anlz_list.append(how_anlz)

        return True

    return True
示例#7
0
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
示例#8
0
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 analyze(wk: AnalyzeWk, how_anlz_list: List[HowToAnalyze]) -> bool:
    """ロックされた候補法

    ある値がそのエリア内に同一行(列)にしか存在しない場合、
    別エリアの行には存在しえない
    ⇒別エリアの同一行のメモを除外出来る
    例>
    エリア[1]において1は(1:1),(1:2)にしか存在出来ない
    +[1]------------------+[2]----------------+[3]----+
    | m=[1,2] i=[1,3] v=4 | m=[1,2] m=[1,3] ? | ? ? ? |
    | m=[2,3] v=5     v=6 | ?       ?       ? | ? ? ? |
    | v=7     v=8     v=9 | ?       ?       ? | ? ? ? |
    +---------------------+-------------------+-------+
    例えばエリア[2]の(1:4)(1:5)のどれかに1が入ってしまうと
    エリア1に1が入る枡がなくなってしまうため矛盾が生じてしまう。
    ※エリア[3]も同様の事が言える
    ⇒エリア[2]の1行目のメモから1を除外出来る
    [補足]
    列に関しても同様のアルゴリズムが適用出来る。
    上記例だとエリア[1]の2に関しても1列目にしか存在しえないため、
    エリア[4]、エリア[7]の1列目のメモ2を除外出来る。

    Args:
        wk (AnalyzeWk): ワーク
        how_anlz_list (List[HowToAnalyze]): 解析方法

    Returns:
        bool: エラーの場合にFalse
    """

    for area in wk.flame.area_list:
        memo_squ_dict: Dict[int, List[Square]] = dict()
        # memo_squ_dict = dict()
        # エリアからメモを抽出
        for squ in area.squ_list:
            for memo in squ.memo_val_list:
                if memo not in memo_squ_dict:
                    memo_squ_dict[memo] = list()
                memo_squ_dict[memo].append(squ)

        for memo, squ_list in memo_squ_dict.items():
            # ロックされた候補法の性質上、対象となる枡は2個または3個のみ
            if len(squ_list) == 2 or len(squ_list) == 3:
                pass
            else:
                continue

            # 対象となる領域ってある?
            target_region: Region = _target_region(squ_list)
            if target_region is None:
                continue

            # 変更対象枡を抽出
            change_squ_list: List[Square] = _find_change_squ(
                wk, memo, target_region, squ_list)

            for change_squ in change_squ_list:

                # メモ除外
                change_squ.memo_val_list.remove(memo)

                # 解析方法生成
                how_anlz: HowToAnalyze = HowToAnalyze(Method.LOCKED_CANDIDATES)
                how_anlz.region = target_region
                how_anlz.changed_squ = change_squ
                how_anlz.remove_memo_list.append(memo)
                how_anlz.trigger_squ_list.extend(squ_list)
                how_anlz.msg = MsgFactory.how_to_locked_candidates(how_anlz)

                how_anlz_list.append(how_anlz)

        # メモしか変更していないため、ループを継続すると別エリアの処理でおかしく可能性がある
        if len(how_anlz_list) > 1:
            return True

    return True
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