KenKen puzzle addends: REDUX A (corrected) non-recursive algorithm

后端 未结 9 1718
眼角桃花
眼角桃花 2021-01-03 05:24

This question relates to those parts of the KenKen Latin Square puzzles which ask you to find all possible combinations of ncells numbers with values x such that 1 <= x &

9条回答
  •  遥遥无期
    2021-01-03 05:59

    Your algorithm seems pretty good at first blush, and I don't think OO or another language would improve the code. I can't say if recursion would have helped but I admire the non-recursive approach. I bet it was harder to get working and it's harder to read but it likely is more efficient and it's definitely quite clever. To be honest I didn't analyze the algorithm in detail but it certainly looks like something that took a long while to get working correctly. I bet there were lots of off-by-1 errors and weird edge cases you had to think through, eh?

    Given all that, basically all I tried to do was pretty up your code as best I could by replacing the numerous C-isms with more idiomatic Python-isms. Often times what requires a loop in C can be done in one line in Python. Also I tried to rename things to follow Python naming conventions better and cleaned up the comments a bit. Hope I don't offend you with any of my changes. You can take what you want and leave the rest. :-)

    Here are the notes I took as I worked:

    • Changed the code that initializes tmp to a bunch of 1's to the more idiomatic tmp = [1] * n_cells.
    • Changed for loop that sums up tmp_sum to idiomatic sum(tmp).
    • Then replaced all the loops with a tmp = + one-liner.
    • Moved raise doneException to init_tmp_new_ceiling and got rid of the succeeded flag.
    • The check in init_tmp_new_ceiling actually seems unnecessary. Removing it, the only raises left were in make_combos_n_cells, so I just changed those to regular returns and dropped doneException entirely.
    • Normalized mix of 4 spaces and 8 spaces for indentation.
    • Removed unnecessary parentheses around your if conditions.
    • tmp[p2] - tmp[p1] == 0 is the same thing as tmp[p2] == tmp[p1].
    • Changed while True: if new_ceiling_flag: break to while not new_ceiling_flag.
    • You don't need to initialize variables to 0 at the top of your functions.
    • Removed combos list and changed function to yield its tuples as they are generated.
    • Renamed tmp to combo.
    • Renamed new_ceiling_flag to ceiling_changed.

    And here's the code for your perusal:

    def initial_combo(ceiling=5, target_sum=13, num_cells=4):
        """
        Returns a list of possible addends, probably to be modified further.
        Starts a new combo list, then, starting from left, fills items to ceiling
        or intermediate between 1 and ceiling or just 1.  E.g.:
        Given ceiling = 5, target_sum = 13, num_cells = 4: creates [5,5,2,1].
        """
        num_full_cells = (target_sum - num_cells) // (ceiling - 1)
    
        combo = [ceiling] * num_full_cells \
              + [1]       * (num_cells - num_full_cells)
    
        if num_cells > num_full_cells:
            combo[num_full_cells] += target_sum - sum(combo)
    
        return combo
    
    def all_combos(ceiling, target_sum, num_cells):
        # p0   points at the rightmost item and moves left under some conditions
        # p1   starts out at rightmost items and steps left
        # p2   starts out immediately to the left of p1 and steps left as p1 does
        #      So, combo[p2] and combo[p1] always point at a pair of adjacent items.
        # d    combo[p2] - combo[p1]; immediate difference
        # cd   combo[p2] - combo[p0]; cumulative difference
    
        # The ceiling decreases by 1 each iteration.
        while True:
            combo = initial_combo(ceiling, target_sum, num_cells)
            yield tuple(combo)
    
            ceiling_changed = False
    
            # Generate all of the remaining combos with this ceiling.
            while not ceiling_changed:
                p2, p1, p0 = -2, -1, -1
    
                while combo[p2] == combo[p1] and abs(p2) <= num_cells:
                    # 3,3,3,3
                    if abs(p2) == num_cells:
                        return
    
                    p2 -= 1
                    p1 -= 1
                    p0 -= 1
    
                cd = 0
    
                # slide_ptrs_left loop
                while abs(p2) <= num_cells:
                    d   = combo[p2] - combo[p1]
                    cd += d
    
                    # 5,5,3,3 or 5,5,4,3
                    if cd > 1:
                        if abs(p2) < num_cells:
                            # 5,5,3,3 --> 5,4,4,3
                            if d > 1:
                                combo[p2] -= 1
                                combo[p1] += 1
                            # d == 1; 5,5,4,3 --> 5,4,4,4
                            else:
                                combo[p2] -= 1
                                combo[p0] += 1
    
                            yield tuple(combo)
    
                        # abs(p2) == num_cells; 5,4,4,3
                        else:
                            ceiling -= 1
                            ceiling_changed = True
    
                        # Resume at make_combo_same_ceiling while
                        # and follow branch.
                        break
    
                    # 4,3,3,3 or 4,4,3,3
                    elif cd == 1:
                        if abs(p2) == num_cells:
                            return
    
                        p1 -= 1
                        p2 -= 1
    
    if __name__ == '__main__':
        print list(all_combos(ceiling=6, target_sum=12, num_cells=4))
    

提交回复
热议问题