5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

パズルAdvent Calendar 2017

Day 2

組合せ最適化でカックロを解く

Last updated at Posted at 2017-12-02

Advent Calendar 1日目の記事 数独を通して組合せ最適化を学ぼう
Advent Calendar 3日目の記事 組合せ最適化でボンバーパズルを解く

これなに

カックロ1を、Pythonで組合せ最適化モデルを作って解きます。
解く楽しみは、モデル化を工夫することになります。

自分でも試してみたい人は、下記を参考にしてください。

問題

下図の白いマスに1~9の数字を入れて、縦または横の合計がヒントと同じになるようにします。

Pythonでは、data.ならば数字のマス)、hint_v(縦の合計)、hint_h(横の合計)を使うことにします。

python
import re, pandas as pd
from pulp import LpProblem, lpDot, lpSum, value
from ortoolpy import addbinvars
data = """\
#..##
....#
..#..
#....
##..#""".splitlines()
hint_v = [ # 開始行、開始列、個数、合計
    (0,1,4,11),
    (0,2,2,4),
    (1,0,2,14),
    (1,3,4,10),
    (2,4,2,3),
    (3,2,2,3),
]
hint_h = [ # 開始行、開始列、個数、合計
    (0,1,2,5),
    (1,0,4,17),
    (2,0,2,6),
    (2,3,2,4),
    (3,1,4,10),
    (4,2,2,3),
]

変数表

下記のような変数表を作成します。各行の変数は0または1をとります。
変数の値が1ならば、該当行 該当列のマスが該当の数になります。

Var
0 0 1 1 v000001
1 0 1 2 v000002
... ... ... ... ...
python
ni,nj = len(data),len(data[0])
a = pd.DataFrame([(i,j,k) for i in range(ni) for j in range(nj)
    if data[i][j]=='.' for k in range(1,10)], columns=list('行列数'))
a['Var'] = addbinvars(len(a))
a[:2]

数理モデルを作り解く

変数表ができたので、カックロの解になるように、制約条件を追加し数理モデルを作成し、解きましょう。

  • 各マスに数字を1つ入れる。 …(1)
    • for _,v in a.groupby(('行','列'))で1つのマスの変数がDataFrameのvに入るので、m += lpSum(v.Var) == 1とすれば、数字を1つ選ぶことになります。
  • 一連の並びで、同じ数字は1つまで。 …(2)
    • 下記では、一連の並びをDataFrame(b)に入れています。一連の並びは、長方形なので、DataFrame.queryで簡単に取り出せます。
  • 一連の並びの合計をヒントと同じに。 …(3)
    • 2列のDataFrameの各列を、それぞれ第1引数、第2引数に渡したいとき、*b.T.valuesでできます。これは、転置(T)してnumpy.arrayvalues)にして展開(*)しています。
python
m = LpProblem()
for _,v in a.groupby(('','')):
    m += lpSum(v.Var) == 1 # (1)
for (di,dj),hint in zip([(1,0),(0,1)],[hint_v,hint_h]):
    for si,sj,nl,sm in hint:
        b = a.query(f'{si}<=行<={si+nl*di}&{sj}<=列<={sj+nl*dj}')[['','Var']]
        for _,v in b.groupby(''):
            m += lpSum(v.Var) <= 1 # (2)
        m += lpDot(*b.T.values) == sm # (3)
m.solve()

結果の表示

python
a['Val'] = a.Var.apply(value)
r = a[a.Val>0.5].[::-1].tolist()
print(re.sub('\\.', lambda _: str(r.pop()), '\n'.join(data)))
>>>
#23##
9512#
51#31
#3142
##21#

解けていることが確認できます。

以上

  1. カックロは株式会社二コリ登録商標です。

5
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?