Python 肯肯拼图加法器:一种(修正的)非递归算法

Python 肯肯拼图加法器:一种(修正的)非递归算法,python,algorithm,statistics,puzzle,combinations,Python,Algorithm,Statistics,Puzzle,Combinations,这个问题与KenKen拉丁方拼图中的那些部分有关,这些部分要求您找到所有可能的NCELL数字与值x的组合,因此1很抱歉,您的代码有点长,并且不是特别可读。如果你能设法总结一下,也许有人能帮你写得更清楚 至于问题本身,我的第一个想法是使用递归。(据我所知,您已经在这样做了。再次为我无法阅读您的代码感到抱歉。)想一种方法,您可以将问题简化为同一问题的一个更小更简单的版本,重复,直到您有一个简单答案的小案例 更具体地说,您有这三个参数,max_val、target_sum和n_单元格。你能把这些数字中

这个问题与KenKen拉丁方拼图中的那些部分有关,这些部分要求您找到所有可能的NCELL数字与值x的组合,因此1很抱歉,您的代码有点长,并且不是特别可读。如果你能设法总结一下,也许有人能帮你写得更清楚

至于问题本身,我的第一个想法是使用递归。(据我所知,您已经在这样做了。再次为我无法阅读您的代码感到抱歉。)想一种方法,您可以将问题简化为同一问题的一个更小更简单的版本,重复,直到您有一个简单答案的小案例

更具体地说,您有这三个参数,max_val、target_sum和n_单元格。你能把这些数字中的一个设置为某个特定的值,以便给你一个非常简单的问题,不需要任何思考吗?一旦你做到了这一点,你能把稍微难一点的问题简化成已经解决的问题吗

编辑:这是我的代码。我不喜欢它执行重复数据消除的方式。我相信还有一种更像蟒蛇的方式。此外,它不允许在一个组合中使用相同的数字两次。若要撤消此行为,只需取出行
(如果n不在numlist:
)。我不确定这是否完全正确,但它似乎有效,而且(IMHO)更具可读性。你可以很容易地添加备忘录,这可能会大大加快它的速度

def get_combos(max_val, target, n_cells):
    if target <= 0:
        return []
    if n_cells is 1:
        if target > max_val:
            return []
        else:
            return [[target]]
    else:
        combos = []
        for n in range(1, max_val+1, 1):
            for numlist in get_combos(max_val, target-n, n_cells-1):
                if n not in numlist:
                    combos.append(numlist + [n])
        return combos

def deduplicate(combos):
    for numlist in combos:
        numlist.sort()
    answer = [tuple(numlist) for numlist in combos]
    return set(answer)

def kenken(max_val, target, n_cells):
    return deduplicate(get_combos(max_val, target, n_cells))
def get_组合(最大值、目标、n个单元格):
如果目标最大值:
返回[]
其他:
返回[[目标]]
其他:
组合=[]
对于范围内的n(1,最大值+1,1):
对于get_组合中的numlist(最大值、目标-n、n_单元格-1):
如果n不在numlist中:
combos.append(numlist+[n])
返回组合
def重复数据消除(组合):
对于组合中的numlist:
numlist.sort()
答案=[组合中numlist的元组(numlist)]
返回集(应答)
def kenken(最大值、目标、n单元):
返回重复数据消除(获取组合(最大值、目标、n个单元格))

乍一看,您的算法似乎相当不错,我认为OO或其他语言不会改进代码。我不能说递归是否有帮助,但我很欣赏非递归方法。我打赌它更难工作,也更难阅读,但它可能更有效,而且肯定相当聪明。老实说,我没有详细分析算法,但它看起来确实需要很长时间才能正常工作。我敢打赌,你一定要仔细考虑过很多零碎的错误和奇怪的边缘案例,嗯

考虑到所有这些,基本上我所要做的就是通过用更惯用的Python ISM替换大量的C-ISM来尽可能地美化您的代码。通常情况下,在C中需要循环的内容可以在Python中的一行中完成。此外,我还尝试重命名东西,以便更好地遵循Python命名约定,并稍微清理了注释。希望我的任何改变都不会冒犯你。你可以拿走你想要的,剩下的就留下。:-)

以下是我工作时做的笔记:

  • 将初始化
    tmp
    的代码更改为一堆1,以使用更惯用的
    tmp=[1]*n_单元格
  • 将求和
    tmp\u sum
    循环更改为惯用的
    sum(tmp)
  • 然后用一个
    tmp=+
    1行程序替换所有的循环
  • raise doneeexception
    移动到
    init\u tmp\u new\u天花板
    ,并清除
    successed
    标志
  • 签入
    init\u tmp\u new\u天花板实际上似乎没有必要。除去它,只剩下
    raise
    s在
    make\u combos\u n\u单元格中,所以我只是将它们改为常规返回,并完全删除了
    doneeexception
  • 用于缩进的4个空格和8个空格的标准化混合
  • 删除了
    if
    条件周围不必要的括号
  • tmp[p2]-tmp[p1]==0
    tmp[p2]==tmp[p1]
    相同
  • 更改为True时:如果新的天花板\u标志:断开
    而不是新的天花板\u标志
  • 您不需要在函数顶部将变量初始化为0
  • 删除了
    组合
    列表,并将函数更改为
    yield
    生成元组时的元组
  • tmp
    重命名为
    combo
  • 新的天花板\u标志重命名为
    天花板\u已更改
下面是供您阅读的代码:

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))
def初始组合(上限=5,目标总和=13,单元格数=4):
"""
返回可能的加数列表,可能需要进一步修改。
启动一个新的组合列表,然后从左开始,将项目填充到天花板
或介于1和天花板之间或仅1之间。例如:
给定上限=5,目标总和=13,单元数=4:创建[5,5,2,1]。
"""
num_full_cells=(target_sum-num_cells)/(上限-1)
combo=[天花板]*num\u full\u单元格\
+[1]*(num_单元格-num_完整单元格)
如果num\u cells>num\u full\u cells:
组合[num\u full\u cells]+=target\u sum-sum(组合)
返回组合
定义所有单元格组合(上限、目标单元格和、单元格数):
#p0指向最右边的项目,在某些情况下向左移动
#p1从最右边的项目开始,然后向左移动
#p2立即从p1的左侧开始,并像p1一样向左移动
#因此,combo[p2]和combo[p1]总是指向一对相邻的项。
#d组合[p2]-组合[p1];直接差额
#cd-combo[p2]-combo[p0];累积离差
#每次迭代,上限将减少1。
尽管如此:
组合=初始组合(上限、目标和、单元格数)
产量元组(组合)
改变的上限=错误
#用t生成所有剩余的组合
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))
def apcnx(n, max_val, target, xsofar=(), sumsofar=0):
  if n==0:
    if sumsofar==target:
      yield xsofar
    return

  if xsofar:
    minx = xsofar[-1] - 1
  else:
    minx = 0

  for x in xrange(minx, max_val):
    for xposs in apcnx(n-1, max_val, target, xsofar + (x+1,), sumsofar+x+1):
      yield xposs

for xs in apcnx(4, 6, 12):
  print xs
(1, 1, 4, 6)
(1, 1, 5, 5)
(1, 2, 3, 6)
(1, 2, 4, 5)
(1, 3, 3, 5)
(1, 3, 4, 4)
(2, 2, 2, 6)
(2, 2, 3, 5)
(2, 2, 4, 4)
(2, 3, 3, 4)
(3, 3, 3, 3)
from __future__ import division
from math import ceil

def make_combos(max_val,target_sum,n_cells):
    combos = []
    # The highest possible value of the next cell is whatever is 
    # largest of the max_val, or the target_sum minus the number 
    # of remaining cells (as you can't enter 0).
    highest = min(max_val, target_sum - n_cells + 1)
    # The lowest is the lowest number you can have that will add upp to 
    # target_sum if you multiply it with n_cells.
    lowest = int(ceil(target_sum/n_cells))
    for x in range(highest, lowest-1, -1):
        if n_cells == 1: # This is the last cell, no more recursion.
            combos.append((x,))
            break
        # Recurse to get the next cell:
        # Set the max to x (or we'll get duplicates like
        # (6,3,2,1) and (6,2,3,1), which is pointless.
        # Reduce the target_sum with x to keep the sum correct.
        # Reduce the number of cells with 1.
        for combo in make_combos(x, target_sum-x, n_cells-1):
            combos.append((x,)+combo)
    return combos

if __name__ == '__main__':
    import pprint
    # And by using pprint the output gets easier to read
    pprint.pprint(make_combos( 6,12,4))
from __future__ import division
from math import ceil

def make_combos(max_val,target_sum,n_cells):
    highest = min(max_val, target_sum - n_cells + 1)
    lowest = int(ceil(target_sum/n_cells))
    for x in xrange(highest, lowest-1, -1):
        if n_cells == 1:
            yield (x,)
            break
        for combo in make_combos(x, target_sum-x, n_cells-1):
            yield (x,)+combo

if __name__ == '__main__':
    import pprint
    pprint.pprint(list(make_combos( 6,12,4)))
def GetFactors(maxVal, noOfCells, targetSum):
    l = []
    while(maxVal != 0):
        remCells = noOfCells - 1
        if(remCells > 2):
            retList = GetFactors(maxVal, remCells, targetSum - maxVal)
            #Append the returned List to the original List
            #But first, add the maxVal to the start of every elem of returned list.
            for i in retList:
                i.insert(0, maxVal)
            l.extend(retList)

        else:
            remTotal = targetSum - maxVal
            for i in range(1, remTotal/2 + 1):
                itemToInsert = remTotal - i;
                if (i > maxVal or itemToInsert > maxVal):
                    continue
                l.append([maxVal, i, remTotal - i])
        maxVal -= 1
    return l



if __name__ == "__main__":
    l = GetFactors(5, 5, 15)
    print l
const int max = 6;
int sol[N_CELLS];

void enum_solutions(int target, int n, int min) {
  if (target == 0 && n == 0)
    report_solution(); /* sol[0]..sol[N_CELLS-1] is a solution */
  if (target <= 0 || n == 0) return; /* nothing further to explore */
  sol[n - 1] = min; /* remember */
  for (int i = min; i <= max; i++)
    enum_solutions(target - i, n - 1, i);
}

enum_solutions(12, 4, 1);
def descending(v):
  """Decide if a square contains values in descending order"""
  return list(reversed(v)) == sorted(v)

def latinSquares(max_val, target_sum, n_cells):
  """Return all descending n_cells-dimensional squares,
     no cell larger than max_val, sum equal to target_sum."""
  possibilities = itertools.product(range(1,max_val+1),repeat=n_cells)
  for square in possibilities:
    if descending(square) and sum(square) == target_sum:
      yield square
for m in latinSquares(6, 12, 4):
  print m
def latinSquares(max_val, target_sum, n_cells):
  if n_cells == 1:
    assert(max_val >= target_sum >= 1)
    return ((target_sum,),)
  else:
    lower_bound = max(-(-target_sum / n_cells), 1)
    upper_bound = min(max_val, target_sum - n_cells + 1)
    assert(lower_bound <= upper_bound)
    return ((v,) + w for v in xrange(upper_bound, lower_bound - 1, -1)
                     for w in latinSquares(v, target_sum - v, n_cells - 1))
for m in latinSquares(6,12,4):
  print m