Dark Magus ([info]_darkus_) wrote,
@ 2006-09-29 10:52:00
Previous Entry  Add to memories!  Tell a Friend!  Next Entry
Entry tags:ФП

Задача по ФП: Магические квадраты
Один товарищ поинтересовался о том, как на языке Haskell устраивать переборы для, к примеру, решения задачи о построении магического квадрата 3 х 3 (такого, что сумма чисел от 1 до 9 в его клетках в рядах, столбцах и диагоналях была бы одинаковой). В качестве примера решения подобной задачи привёл «изящную» запись на языке Prolog. Таки только что нашёл время, чтобы реализовать простейшую переборную функцию на языке Haskell для тех же целей. Вот она:

module MagicSquare where

(//) :: Eq a => [a] -> a -> [a]
[] // y     = []
(x:xs) // y = if (x == y) then xs
                          else x:(xs // y)

(///) :: Eq a => [a] -> [a] -> [a]
s /// [] = s
s /// (x:xs) = (s // x) /// xs

magicSquares = [[x1, x2, x3, y1, y2, y3, z1, z2, z3] | x1 <- [1..9],
                                                       x2 <- [1..9] /// [x1],
                                                       x3 <- [1..9] /// [x1, x2],
                                                       y1 <- [1..9] /// [x1, x2, x3],
                                                       y2 <- [1..9] /// [x1, x2, x3, y1],
                                                       y3 <- [1..9] /// [x1, x2, x3, y1, y2],
                                                       z1 <- [1..9] /// [x1, x2, x3, y1, y2, y3],
                                                       z2 <- [1..9] /// [x1, x2, x3, y1, y2, y3, z1],
                                                       z3 <- [1..9] /// [x1, x2, x3, y1, y2, y3, z1, z2],
                                                       x1 + x2 + x3 == y1 + y2 + y3,
                                                       x1 + x2 + x3 == z1 + z2 + z3,
                                                       x1 + x2 + x3 == x1 + y1 + z1,
                                                       x1 + x2 + x3 == x2 + y2 + z2,
                                                       x1 + x2 + x3 == x3 + y3 + z3,
                                                       x1 + x2 + x3 == x1 + y2 + z3,
                                                       x1 + x2 + x3 == x3 + y2 + z1]


Вспомогательные операции (//) и (///) используются для получения списка с исключённым из него элементом и подсписком соответственно. Впрочем, можно видеть, что решение далеко от изящества. Полный перебор из девяти вложенных друг в друга циклов с семью условиями внутри — это жесть. Кроме того, задача решена очень узко — ищутся только магические квадраты размера 3 х 3. Это мало — интерпретатор выдал 8 решений, которые тождественны друг другу с точностью до поворотов и отражений, а потому сия задача не совсем интересна:

2 7 6
9 5 1
4 3 8

И вот вам, адепты ФП из моих читателей, задачка для тренировки мозгов. Решить общую задачу по построению магических квадратов. Само собой, что сумма чисел в ячейках по горизонтали, вертикали и диагонали должна совпадать везде. Кроме того, можно добавить изыску в задачу — пра́вила, описывающие «магичность» квадрата, должны задаваться в некоей функции.

Впрочем, имеется алгоритм для построения магических квадратов с нечётным размером стороны. Только мне не известно, является ли построенный по этому алгоритму магический квадрат единственным (с точностью до поворотов и отражений, естественно).


(Post a new comment)


travmaturg
2006-09-29 07:55 am UTC (link)
Навскидку предлагаю оптимизацию: сначала вычислить сумму чисел в строчках/столбцах/диагоналях, а потом сравнивать с ней. Это должно сильно сократить перебор.

(Reply to this)(Thread)


[info]_darkus_
2006-09-29 11:29 am UTC (link)
Конечно. Сумма чисел в строчках/столбцах/диагоналях вычисляется просто:

s = sum [1..n] / n

(Reply to this)(Parent)


[info]lomeo
2006-09-29 09:19 am UTC (link)
Замечания.

Во первых, в модуле List уже есть необходимые функции:

(//) :: Eq a => [a] -> a -> [a]
(//) = flip delete

(///) :: Eq a => [a] -> [a] -> [a]
(///) = (\\)


Во вторых при правильной расстановке не будет лишних выборок (циклов)


magicSquares = [[x1, x2, x3, y1, y2, y3, z1, z2, z3] |
    x1 <- [1..9],
    x2 <- [1..9] /// [x1],
    x3 <- [1..9] /// [x1, x2],
    y1 <- [1..9] /// [x1, x2, x3],
    z1 <- [1..9] /// [x1, x2, x3, y1],
    x1 + x2 + x3 == x1 + y1 + z1,
    y2 <- [1..9] /// [x1, x2, x3, y1, z1],
    x1 + x2 + x3 == x3 + y2 + z1,
    y3 <- [1..9] /// [x1, x2, x3, y1, y2, z1],
    x1 + x2 + x3 == y1 + y2 + y3,
    z2 <- [1..9] /// [x1, x2, x3, y1, y2, y3, z1],
    x1 + x2 + x3 == x2 + y2 + z2,
    z3 <- [1..9] /// [x1, x2, x3, y1, y2, y3, z1, z2],
    x1 + x2 + x3 == z1 + z2 + z3,
    x1 + x2 + x3 == x3 + y3 + z3,
    x1 + x2 + x3 == x1 + y2 + z3]


Сравни скорости исполнения!

В третьих, можно еще оптимизировать - убирать по одному элементу, а не по группе:

    let xs1 = [1..9],
    x1 <- xs1,
    let xs2 = xs1 // x1,
    x2 <- xs2,
    и т.д.


Здесь это не критично, но для больших (ударение в любом месте ;)) квадратов может иметь смысл.
Насчёт общего перебора мысли есть, реализовывать времени нет. Может чуть позже.

Спасибо за задачку!

(Reply to this)(Thread)


[info]_darkus_
2006-09-29 11:28 am UTC (link)
Ну само собой, что здесь широчайшее поле для оптимизаций. То, что приведено здесь, — придумано и написано за пять минут.

(Reply to this)(Parent)


[info]_darkus_
2006-09-29 11:42 am UTC (link)
Да, кстати, я знал, конечно, что в каком-то модуле есть функции для операций со списками. Даже в Prelude есть что-то. Но лень было искать, а эти функции настолько просты, что написать их — одно удовольствие.

(Reply to this)(Parent)(Thread)


[info]lomeo
2006-09-29 11:45 am UTC (link)
Может быть если мы хорошо поищем, то найдём функцию для построения магических квадратов? :-)

Насчет поиска. Есть классная вещь http://www.haskell.org/hoogle/
Ищет как по имени функции, так и по её типу.

(Reply to this)(Parent)(Thread)


[info]_darkus_
2006-09-29 11:48 am UTC (link)
Да есть такие функции, в исходной теме даже ссылка на какой-то модуль есть.

А про поиск ты говорил мне уже :).

(Reply to this)(Parent)

это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-09-29 09:02 pm UTC (link)
занимаюсь этими квадратами уже лет 5 (pascal, delhpi, теперь vc++).. построение перебором - пустая трата времени, составлял я такие алгоритмы, для матрицы 5*5 уже нет никакой возможности дождаться полного перебора :). а для 4*4 известны все квадраты, не помню точно, но около 8000. есть способы вспомогательного построения, когда по квадрату построенному основным способом идет перебор, но перебор рекомбинаций/взаимным_обменом строк/столбцов, тогда получаются производные квадраты. для матриц большой мерности их сотни тысяч, потому что от производного квадрата можно искать свои производные итп. просто приходится проверять получившиеся квадраты на повторы.
++есть магические квадраты у которых магическая_сумма есть и по ломанным диаганолям (ищутся рекомбинацией))
++есть маг.квадраты у которых одинаковая сумма у подквадратов (ищутся также)
++есть магические квадраты трансформируемые в магические кубы (для матриц мерностью 8*8, 27*27 итд, (ищутся почти также)), а также дополнительно отвечающие другим условиям магичности

для квадрата 3*3 есть только одно решение +отраженные квадраты :)

у меня на сишнике алгоритм построения матрицы любой мерности (от 3х)выглядит так, может не очень красиво местами, ибо я этот код не вычищал (ф-я GenerateLinear_**** просто строит линейную матрицу):
MATRIXGEN_Terras (для нечетных)
MATRIXGEN_Exponent (для четной мерности где N степень двойки)
MATRIXGEN_RaussBoll (для любых четных)

int Generate(int MatrixGenMethod)
{
if ( Header.SizeX < 3 ) return RUNCODE_Error;
MatrixItem_Base a = 0;
int i, j;
int size = Header.SizeX;
switch(MatrixGenMethod)
{
//-----------------------------------------------------------
case MATRIXGEN_Linear_LRUD:
GenerateLinear_LRUD();
return RUNCODE_Done;
case MATRIXGEN_Linear_RLUD:
GenerateLinear_RLUD();
return RUNCODE_Done;
//----------------------------------------------------------
case MATRIXGEN_Terras:
int x, y, stx, sty;
if ( (size %2)==0 ) return RUNCODE_Error;

x = stx = size >> 1;
y = sty = -x;

for (a=1; a <= Header.ItemCount;
a++, x++, y++, x==(stx+size) ? (x=--stx,y=++sty):x)
{
i=x >= size ? x - size: x;
j=y >= size ? y - size: y;
i < 0 ? i = x + size: i;
j < 0 ? j = y + size: j;
Items [j*size + i] = a;
};
Header.Info = _MTXINF_MageBase;
return RUNCODE_Done;
//----------------------------------------------------------

(Reply to this)

это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-09-29 09:03 pm UTC (link)
case MATRIXGEN_RaussBoll:
{
int k,m,r,s,b;
MatrixItem_Base t;
if ( size%2 ) return RUNCODE_Error;

GenerateLinear_LRUD();

m = size >> 1;

if (size % 4 == 0) //e-e
{
j = 1;
for (i=0; i < m; i++)
for (k=0; k < (m >> 1); k++, j+=2)
{
j == (m) ? j=1: j;
j == (m+1) ? j=0: j;
s=size-i-1;
b=size-j-1;
t=Items[i+ size*j];
Items[i+ size*j]=Items[s+size*b];
Items[s+size*b]=t;
t=Items[i+size*b];
Items[i+size*b]=Items[s+size*j];
Items[s+size*j]=t;
}
} else
if ((size % 4) == 2) //e-o
{ m=size >> 1;
r=(m-1) >> 1;
for (i=0; i
[Error: Irreparable invalid markup ('<m;>') in entry. Owner must fix manually. Raw contents below.]

case MATRIXGEN_RaussBoll:
{
int k,m,r,s,b;
MatrixItem_Base t;
if ( size%2 ) return RUNCODE_Error;

GenerateLinear_LRUD();

m = size >> 1;

if (size % 4 == 0) //e-e
{
j = 1;
for (i=0; i < m; i++)
for (k=0; k < (m >> 1); k++, j+=2)
{
j == (m) ? j=1: j;
j == (m+1) ? j=0: j;
s=size-i-1;
b=size-j-1;
t=Items[i+ size*j];
Items[i+ size*j]=Items[s+size*b];
Items[s+size*b]=t;
t=Items[i+size*b];
Items[i+size*b]=Items[s+size*j];
Items[s+size*j]=t;
}
} else
if ((size % 4) == 2) //e-o
{ m=size >> 1;
r=(m-1) >> 1;
for (i=0; i<m; i++)
for (k=0, j=i; k<r; k++, j++)
{ if (j>=m) j=0;
s=size-i-1;
b=size-j-1;
t=Items[i+size*j];
Items[i+size*j]=Items[s+size*b];
Items[s+size*b]=t;
t=Items[i+size*b];
Items[i+size*b]=Items[s+size*j];
Items[s+size*j]=t;
}

for (k=0, i=0, j=r; k<m; k++, i++, j++)
{
if (j>=m) j=0;
s=size-i-1;
t=Items[i+size*j];
Items[i+size*j]=Items[s+size*j];
Items[s+size*j]=t;
};

for (k=0, i=0, j=r+1; k<m; k++, i++, j++)
{
if (j>=m) j=0;
s=size-j-1;
t=Items[i+size*j];
Items[i+size*j]=Items[i+size*s];
Items[i+size*s]=t;
}
}
}
return RUNCODE_Done;
//----------------------------------------------------------
case MATRIXGEN_Exp:
{
int b,s;
MatrixItem_Base a;

for (b=1; b < 0xFFFF; b <<= 1)
{
if (b==size) break;
if (b>size) return RUNCODE_Error;
};

for (i=0, a=1, s=0; i<size; i++)
{
b = s==0 ? 2: 0;
s = s==3 ? b=2, 0: s+1;
for (j=0; j<size; j++, a++)
{
if (b==0) Items[j+i*size]=a;
b = b==3 ? Items[j+i*size]=a, 0: b+1;
}
}

a=size*size;
for (i=0, s=2; i<size; i++)
{
b = s==0 ? 2: 0;
s = s==3 ? b=2, 0: s+1;
for (j=0; j< size; j++, a--)
{
if (b==0) Items[j+i*size]=a;
b = b==3 ? Items[j+i*size]=a, 0: b+1;
}
}
}
return RUNCODE_Done;
} // switch
return RUNCODE_Error;
}

есть еще один переборный способ для нечетных квадратов, когда перебор осуществялется по 4 числам в диапазоне от -(N-1) до (N-1), где N-мерность. для каждой комбинации 4х чисел проверяется N^2 вариантов (с попыткой построения из всех клеток матрицы). так находится огромное количество квадратов. на с++ мне это не нужно было а в дельфи это было так:

PROCEDURE SQRGEN_STEP (Var SQR:T_MageSQR; N :LongInt;
STX,STY:LongInt;incX,incY,incXprep,incYprep:LongInt);
Var i,j,a :LongInt;
Begin
FillChar(SQR.NUMS,SizeOf(SQR.NUMS),0);
a:=0;
if n>2 then
Begin
Repeat
Inc(a);
SQR.NUMS[STX,STY]:=a;

i:=StX;
j:=StY;

StX:=StX+incX;
StY:=StY+incY;

if StX>n then StX:=StX mod N;
while StX<1 do StX:=StX+N;

if StY>n then StY:=StY mod N;
while StY<1 do StY:=StY+N;

if SQR.nums[StX,StY]<>0 then
begin
i:=i+incXprep;
j:=j+incYprep;
StX:=i;
StY:=j;

if StX>n then StX:=StX mod N;
while StX<1 do StX:=StX+N;

if StY>n then StY:=StY mod N;
while StY<1 do StY:=StY+N;

if a < N*N then
if SQR.NUMS[StX,StY]<>0 then EXIT;
end;
UNTIL a=N*N;

SQR.Size:=N;
End;
End;

не.. на самом деле алгоритмов их построения вообще немало, но алгоритм построения террасами наиболее интересен так как всегда дает свастики :)

а вообще тут подборка графиков магических квадратов http://tetragor.livejournal.com/tag/mage_squares

(Reply to this)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-09-30 11:24 am UTC (link)
Вы — МонстрЪ. Только больно уж этот код негож, честно скажу :) Императивности много. Вот функциональности бы в него добавить, вообще бы цены не было.

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-10-01 02:13 pm UTC (link)
главное то что он работает :) мыслить в терминах функциональной парадигмы не привык. я вообще както не въезжал в суть и выгоды ФП... в смысле вообще не разбирался с ним.

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-10-01 02:17 pm UTC (link)
Записывайтесь на книжку, тогда...

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-10-02 01:54 am UTC (link)
Я как программист вырос на ассемблере, программируя полиморфные движки, поэтому не по наслышке знаю ограничения накладываемые фон-неймановской архитектурой, и что из этого следует. ФП это другой подход к представлению одного и того же алгоритма, и гораздо более рессурсозатратный, а скорость выполнения мне важна, все что может ФП я могу сделать императивными средствами. ФП подход совершенно лишний для моих целей, поэтому мне его изучать и вникать в него бессмысленно. В данный момент программирую медиа генератор/обработчик представляющий собой набор модулей с разнообразными типами входных/выходных данных, которые передаются между параметрами модулями по собираемой схеме для аудио/видео синтеза (в первую очередь основываясь на магических квадратах, так как они сверх-симметричны по самым различным параметрам). Можно глянуть как выглядит этот синтезатор тут.
Скорость рил-тайм обработки потоков данных (причем разных типов) тут слишком важна, что бы можно было реализовывать это средствами ФП языков. Да и вообще такой тип задач насколько я понимаю просто не решается с помощью ФП, поправьте если ошибаюсь..

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-10-02 06:47 am UTC (link)
Да уж. Видимо, у меня Вам нечему научиться :)

По поводу ФП — универсальная парадигма. Хотя в некоторых местах действительно проигрывает императивной. Пока...

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-10-02 05:43 pm UTC (link)
Есть чему. Но это мне ни к чему :)

(Reply to this)(Parent)

Re: это так вкратце, может на какие мысли натолкнет
[info]tetragor
2006-10-02 05:45 pm UTC (link)
Смертен я. Нельзя время терять на то что в обозримом будущем не имеет практического применения. На это нет времени.

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-10-02 06:00 pm UTC (link)
Да. Тоже верная позиция...

(Reply to this)(Parent)

Re: это так вкратце, может на какие мысли натолкнет
[info]clegger
2006-10-03 06:02 am UTC (link)
кстати функ. парадигма становится все более и более популярной в наше время, так что время можно и потратить.
а вообще в мире есть множество вещей которые делаются не для кого-то а для себя.
я только закомлюсь с миром ФП, но уже сейчас я понимаю что я четче формулирую мысли даже на императивных языках.

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-10-03 06:04 am UTC (link)
Последнее предложение, честно говоря, я не совсем понял :)

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]clegger
2006-10-03 09:34 am UTC (link)
субъективно:
просто функциональная парадигма прививает некую "стройность" мышления. то есть даже для програмиста на императивном языке ознакомление с функ. подходами очень полезно.
вот мне уже сейчас не хватает в Delphi (пишу по работе проект) хвостовой рекурсии и лямбда-выражений (это как минимум) :-)

(Reply to this)(Parent)(Thread)

Re: это так вкратце, может на какие мысли натолкнет
[info]_darkus_
2006-10-03 09:47 am UTC (link)
Понятно. Теперь всё понял.

(Reply to this)(Parent)


Create an Account
Forgot your login?
Login w/ OpenID
English • Español • Deutsch • Русский…