4

この質問は、1 <= x <= maxval および x(1) + ... + x(ncells) =目標合計。いくつかのより有望な回答をテストしたので、回答賞を Lennart Regebro に授与します。理由は次のとおりです。

  1. 彼のルーティンは私のものと同じくらい速い (+-5%)。

  2. 彼は、私の元のルーチンのどこかにバグがあることを指摘しました。ありがとう、レナート。

chrispy は、Lennart のものと同等と思われるアルゴリズムを提供しましたが、5 時間後、すっごく、最初にネットワークに到達しました。

備考: Alex Martelli の必要最小限の再帰アルゴリズムは、考えられるすべての組み合わせを作成し、それらすべてをふるいにかけ、どれが穴を通過するかを確認する例です。このアプローチは、Lennart や私のアプローチよりも 20 倍以上時間がかかります。(入力を max_val = 100、n_cells = 5、target_sum = 250 に上げます。私のボックスでは 18 秒対 8+ 分です。) 道徳: 考えられるすべての組み合わせを生成しないのは良いことです。

別の注意: Lennart のルーチンと私のルーチンは、同じ順序で同じ回答を生成します。実際、それらは異なる角度から見た同じアルゴリズムですか? 知らない。

何かが思い浮かびます。たとえば、(8,8,2,1,1) で始まり (4,4,4,4,4) で終わるように回答を並べ替えると (max_val=8, n_cells=5, target_sum で得られるもの) =20)、シリーズは一種の「最も遅い降下」を形成し、最初のものは「ホット」で、最後のものは「コールド」であり、その間に可能な最大数のステージがあります. これは「情報エントロピー」に関連していますか?それを見るための適切な指標は何ですか?熱の降順 (または昇順) で組み合わせを生成するアルゴリズムはありますか? (これは、正規化された std.dev を見ると、短いストレッチで近いですが、私が見る限りではありません。)

Python ルーチンは次のとおりです。

#!/usr/bin/env python
#filename: makeAddCombos.07.py -- stripped for StackOverflow

def initialize_combo( max_val, n_cells, target_sum):
    """returns combo
    Starting from left, fills combo to max_val or an intermediate value from 1 up.  
    E.g.:  Given max_val = 5, n_cells=4, target_sum = 11, creates [5,4,1,1].
    """
    combo = []
    #Put 1 in each cell.
    combo += [1] * n_cells
    need = target_sum - sum(combo)
    #Fill as many cells as possible to max_val.
    n_full_cells = need //(max_val - 1)
    top_up = max_val - 1
    for i in range( n_full_cells): combo[i] += top_up
    need = target_sum - sum(combo)
    # Then add the rest to next item.
    if need > 0:
        combo[n_full_cells] += need
    return combo
#def initialize_combo()

def scrunch_left( combo):
    """returns (new_combo,done)
    done   Boolean; if True, ignore new_combo, all done;
            if Falso, new_combo is valid.

    Starts a new combo list.  Scanning from right to left, looks for first
    element at least 2 greater than right-end element.  
    If one is found, decrements it, then scrunches all available counts on its
    right up against its right-hand side.  Returns the modified combo.
    If none found, (that is, either no step or single step of 1), process
    done.
    """
    new_combo = []
    right_end = combo[-1]
    length = len(combo)
    c_range = range(length-1, -1, -1)
    found_step_gt_1 = False
    for index in c_range:
        value = combo[index]
        if (value - right_end) > 1:
            found_step_gt_1 = True
            break
    if not found_step_gt_1:
        return ( new_combo,True)

    if index > 0:
        new_combo += combo[:index]
    ceil = combo[index] - 1
    new_combo += [ceil]
    new_combo += [1] * ((length - 1) - index)
    need = sum(combo[index:]) - sum(new_combo[index:])
    fill_height = ceil - 1
    ndivf = need // fill_height
    nmodf = need % fill_height
    if ndivf > 0:
        for j in range(index + 1, index + ndivf + 1):
            new_combo[j] += fill_height
    if nmodf > 0:
        new_combo[index + ndivf + 1] += nmodf
    return (new_combo, False)
#def scrunch_left()

def make_combos_n_cells_ge_two( combos, max_val, n_cells, target_sum):
    """
    Build combos, list of tuples of 2 or more addends.
    """
    combo = initialize_combo( max_val, n_cells, target_sum)
    combos.append( tuple( combo))
    while True:
        (combo, done) = scrunch_left( combo)
        if done:
            break
        else:
            combos.append( tuple( combo))
    return combos
#def make_combos_n_cells_ge_two()

if __name__ == '__main__':

    combos = []
    max_val     = 8
    n_cells     = 5
    target_sum  = 20
    if n_cells == 1: combos.append( (target_sum,))
    else:
        combos = make_combos_n_cells_ge_two( combos, max_val, n_cells, target_sum)
    import pprint
    pprint.pprint( combos)
4

9 に答える 9

3

あなたのアルゴリズムは一見するとかなり良いように見えます.OOや別の言語がコードを改善するとは思いません. 再帰が役に立ったかどうかはわかりませんが、再帰的でないアプローチには感心します。作業するのは難しく、読むのも難しいに違いありませんが、おそらくより効率的であり、間違いなく非常に賢いです。正直なところ、アルゴリズムを詳細に分析したわけではありませんが、正しく機能するまでに長い時間がかかったように見えます。考え抜かなければならない 1 桁違いのエラーや奇妙なエッジ ケースがたくさんあったに違いありません。

以上のことから、基本的に私が試みたのは、多数の C イズムをより慣用的な Python イズムに置き換えることで、コードをできる限りきれいにすることだけでした。多くの場合、C でループを必要とすることは、Python では 1 行で実行できます。また、Python の命名規則によりよく従うように名前を変更し、コメントを少し整理しました。私の変更であなたを怒らせないことを願っています。必要なものを取り、残りを残すことができます。:-)

私が作業中に取ったメモは次のとおりです。

  • 一連の 1に初期化するコードtmpを、より慣用的な に変更しましたtmp = [1] * n_cells
  • 慣用句に要約される変更forされたループ。tmp_sumsum(tmp)
  • tmp = <list> + <list>次に、すべてのループをワンライナーに置き換えました。
  • に移動raise doneExceptionしてフラグinit_tmp_new_ceilingを取り除きました。succeeded
  • チェックインはinit_tmp_new_ceiling実際には不要のようです。それを削除すると、raise残ったのはだけだったmake_combos_n_cellsので、それらを通常のリターンに変更してdoneException完全に削除しました。
  • インデント用の 4 つのスペースと 8 つのスペースの正規化された混合。
  • 条件を囲む不要な括弧を削除しましたif
  • tmp[p2] - tmp[p1] == 0と同じですtmp[p2] == tmp[p1]
  • while True: if new_ceiling_flag: breakに変更while not new_ceiling_flag
  • 関数の先頭で変数を 0 に初期化する必要はありません。
  • combosリストが削除され、関数yieldが生成されたタプルに変更されました。
  • tmpに改名combo
  • new_ceiling_flagに改名ceiling_changed

そして、これがあなたの熟読のためのコードです:

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))
于 2009-06-30T04:34:45.397 に答える
2

まず第一に、コードが理解できるように、何かを意味する変数名を使用します。次に、問題を理解した後、それは明らかに再帰的な問題です.1つの数値を選択すると、残りの正方形の可能な値を見つける問題はまったく同じ問題ですが、値が異なります.

だから私はこのようにします:

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))

また、あなたのソリューションはまだバグがあるようです。例として、max_val=8, target_sum=20 and n_cells=5コードが解決策を見つけられない値について。(8,6,4,1,1,)これがルールを見逃したことを意味するかどうかはわかりませんが、有効なオプションであるべきルールを理解しているためです。

これはジェネレーターを使用したバージョンです。値が非常に大きい場合は数行とメモリを節約できますが、再帰としてジェネレーターを「取得」するのは難しい場合があります。

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)))
于 2009-06-30T05:37:53.983 に答える
2

これは、「1 <= x <= max_val および x(1) + ... + x(n) = target となるような値 x を持つ n 個の数値のすべての可能な組み合わせを見つける」と考えることができる最も単純な再帰的ソリューションです。ゼロから開発しています。簡単にするために、最適化をまったく行わないバージョンを次に示します。

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

基本ケースn==0(これ以上数値を生成できない場合) では、条件を満たしていればそれまでのタプルを生成するか、何も生成せずに終了します (戻ります)。

これまでに作成したよりも長いタプルを生成することになっている場合は、if/else繰り返しを避けるために、減少しないタプルのみを生成するようにします (「順列」ではなく「組み合わせ」と言いました)。

for、「この」アイテムのすべての可能性を試し、次の下位レベルの再帰がまだ生成できるものは何でもループします。

私が見る出力は次のとおりです。

(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)

これは正しいようです。

無数の可能な最適化がありますが、覚えておいてください:

最初に機能させ、次に高速化する

私はケント・ベックと連絡を取り、この引用を「Python in a Nutshell」に適切に帰属させました。彼は、実際にはプログラミングとは無関係の仕事をしている父親からそれを受け取ったと私に言いました;-)。

この場合、重要な問題は何が起こっているのかを理解することであり、最適化が干渉する可能性があるように私には思えます。必要に応じて、最適化されていないこの完全なバージョンで何が起こっているのかを OP が理解できることを確認したら、靴下を最適化します。

于 2009-06-30T04:58:43.767 に答える
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

降順のグリッドのリストを直接列挙することでこのコードを最適化することもできましたが、最初のパス ソリューションとしては itertools.product の方がはるかに明確であることがわかりました。最後に、関数を呼び出します。

for m in latinSquares(6, 12, 4):
  print m
于 2009-06-30T09:58:23.677 に答える
1

そして、これは別の再帰的なジェネレーターベースのソリューションですが、今回は簡単な数学を使用して各ステップで範囲を計算し、不要な再帰を回避しています。

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))

満たすことが不可能なパラメーターを指定すると、このコードは AssertionError で失敗します。これは、不必要な再帰を決して行わないという私の「正しさの基準」の副作用です。その副作用が望ましくない場合は、アサーションを削除してください。

-(-x/y) を使用して除算後に切り上げることに注意してください。それを書くためのよりpythonicな方法があるかもしれません。また、yield の代わりにジェネレータ式を使用していることにも注意してください。

for m in latinSquares(6,12,4):
  print m
于 2009-06-30T10:23:57.523 に答える
1

少し話題から外れますが、それでもケンケンのプログラミングに役立つかもしれません。

Killer Sudoku を解くために DLX アルゴリズムを使用して良い結果を得ました (KenKen と非常に似ていますが、ケージはありますが、合計のみです)。ほとんどの問題に 1 秒もかからず、MATLAB 言語で実装されました。

このフォーラムを参照してください http://www.setbb.com/phpbb/viewtopic.php?t=1274&highlight=&mforum=sudoku

キラー数独「ウィキペディアを見て、ハイパーリンクを投稿することはできません」くそスパマー

于 2009-06-30T13:03:02.180 に答える
1

申し訳ありませんが、あなたのコードは長く、特に読みにくいです。どういうわけかそれを要約しようとすることができれば、誰かがあなたがそれをより明確に書くのを手伝ってくれるかもしれません.

問題自体に関しては、最初に考えたのは再帰を使用することです。(私が知っている限りでは、あなたはすでにそれを行っています。私があなたのコードを読むことができなかったことをもう一度申し訳ありません。) 問題を、同じ問題のより小さな簡単なバージョンに減らす方法を考えてください。非常に単純な答えを持つ些細なケース。

もう少し具体的に言うと、max_val、target_sum、n_cells という 3 つのパラメーターがあります。まったく考える必要のない非常に単純な問題を解くために、これらの数値の 1 つを特定の値に設定できますか? それができたら、問題の少し難しいバージョンを既に解決済みの問題に減らすことができますか?

編集:これが私のコードです。重複除外の方法が好きではありません。もっと Pythonic な方法があると確信しています。また、1 つの組み合わせで同じ番号を 2 回使用することはできません。この動作を元に戻すには、行を削除しますif n not in 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))
于 2009-06-30T04:32:51.030 に答える
1

まず第一に、私は自分で Python を学んでいるので、この解決策は素晴らしいものではありませんが、これはこれを解決しようとする試みにすぎません。私はそれを再帰的に解決しようとしましたが、この種の問題には再帰的な解決策が理想的だと思いますが、その再帰的な解決策はこれではないかもしれません:

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
于 2009-06-30T06:31:16.770 に答える
1

ここでは、C/C++ での簡単なソリューションを示します。

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);
于 2009-06-30T07:10:49.753 に答える