Слишком большое потребление памяти при генерации магических квадратов в erlang. Нужна помощь по оптимизации

для университета я должен реализовать алгоритм, который создает все возможные магические квадраты для заданной длины ребра и определенной суммы. Для n=3 алгоритм работает, как и ожидалось. Но при генерации всех магических квадратов для n=4 через некоторое время мне не хватило памяти. Эта проблема уже упоминалась в описании задачи. Я уже пытался оптимизировать код, но он все еще не работает должным образом. Поэтому я надеюсь, что кто-то может дать мне несколько советов.

Моя основная идея такова: сначала я генерирую все возможные строки, которые я могу использовать с заданными числами, а затем я пытаюсь объединить их таким образом, чтобы были выполнены ограничения магического квадрата. Это происходит через возврат. Я думаю, что проблема в функции makeRows, которая через какое-то время потребляет слишком много памяти для хранения всех строк.

Если вам нужно дополнительное объяснение кода, я могу дать!

magicSquare(N, Value) ->
    Squares = buildSquare(N, makeRows(N, N*N, Value, N)),
    io:fwrite("Squares ready"), io:fwrite("~n"),
    Result = lists:filter(fun(X) -> testsquare(X, N, Value) end, Squares),
    io:write(length(Result)),
    Result.

buildSquare(0, _) -> [[]];
buildSquare(Rows, AvailableRows) ->
    [ [X|L] || L <- buildSquare(Rows-1, AvailableRows), X <- AvailableRows, onlyUniqueNumbers(lists:flatten([X|L]))].

onlyUniqueNumbers(List) -> erlang:length(List) == sets:size(sets:from_list(List)).

%produces all possible rows with a dimension of Fields and the Numbers from 1 to Numbers and the right sum for each row
makeRows(0,_,_,_) -> [[]];
makeRows(Fields, Numbers, Value, TargetLength) ->
    [ [X|L] || X <- makeRows(Fields-1, Numbers, Value, TargetLength), L <- lists:seq(1,Numbers), checkRow([X|L], TargetLength, Value)].

checkRow(Row, Length, Value) when length(Row) < Length -> true;
checkRow(Row, Length, Value) ->
    Sum = lists:sum(Row),
    if Sum == Value -> true;
    true -> false
    end.

testsquare(Square, N, Value) -> checkAllDiagonal(Square, Value) andalso checkAllHorizontal(Square, Value) andalso checkAllVertical(Square, N, Value).

checkAllHorizontal([H|T], Value) ->
    case checkHorizontal(H, Value, 0) of
        true -> checkHorizontal(lists:nth(1, T), Value, 0);
        false -> false
    end;
checkAllHorizontal([], Value) -> true.

checkHorizontal([H|T], Value, Summe) -> checkHorizontal(T, Value, Summe + H);
checkHorizontal([], Value, Summe) when Summe == Value -> true;
checkHorizontal([], Value, Summe) -> false.

checkAllVertical(Square, N, Value) -> checkAllVertical(Square, N, Value, 1).
checkAllVertical(Square, N, Value, Column) ->
    if
        Column > N -> true;
        true ->
            case checkVertical(Square, Value, 0, Column) of
                true -> checkAllVertical(Square, N, Value, Column + 1);
                false -> false
            end
    end.

checkVertical([], Value, Summe, Column) when Summe == Value -> true;
checkVertical([], Value, Summe, Column) -> false;
checkVertical([H|T], Value, Summe, Column) -> checkVertical(T, Value, Summe + lists:nth(Column, H), Column).

checkAllDiagonal(Square, Value) ->
    case checkDiagonal(Square, Value, 0, 1,1) of
        true -> case checkDiagonal(Square, Value, 0, length(lists:nth(1, Square)),-1) of
                            true -> true;
                            false -> false
                        end;
        false -> false
    end.

checkDiagonal([H|T], Value, Summe, Position, Richtung) -> checkDiagonal(T, Value, Summe + lists:nth(Position, H), Position + Richtung, Richtung);
checkDiagonal([], Value, Summe, Position, Richtung) when Summe == Value -> true;
checkDiagonal([], Value, Summe, Position, Richtung) -> false.

Хорошо, я попытался добавить проверки для строк и квадратов ранее в процессе расчета. Вот измененные функции.

buildSquare(0, _, _, _) -> [[]];
buildSquare(Rows, AvailableRows, RowLength, Value) ->
    [ [X|L] || L <- buildSquare(Rows-1, AvailableRows, RowLength, Value), X <- AvailableRows, validateSquare([X|L], RowLength, Value)].

checkOnlyUniqueNumbers(List) -> erlang:length(lists:flatten(List)) == sets:size(sets:from_list(lists:flatten(List))).

validateSquare(List, RowLength, Value) when length(List) == RowLength -> testsquare(List, RowLength, Value) andalso checkOnlyUniqueNumbers(List);
validateSquare(List, _,_) -> checkOnlyUniqueNumbers(List).

%produces all possible rows with a dimension of Fields and the Numbers from 1 to Numbers
makeRows(0,_,_,_) -> [[]];
makeRows(Fields, Numbers, Value, TargetLength) ->
    [ [X|L] || L <- makeRows(Fields-1, Numbers, Value, TargetLength), X <- lists:seq(1,Numbers), checkRow([X|L], TargetLength, Value)].

%Checks if the sum of the row is Value when the row has the needed length Length
checkRow(Row, Length, _) when length(Row) < Length -> checkOnlyUniqueNumbers(Row);
checkRow(Row, _, Value) ->
    Sum = lists:sum(Row),
    Sum == Value andalso checkOnlyUniqueNumbers(Row).

person soupdiver    schedule 13.12.2012    source источник
comment
да, просто прокрутите вниз в кодовом мальчике, я думаю.   -  person soupdiver    schedule 13.12.2012
comment
Можем ли мы добавить параллелизм здесь? Эрланговые ниндзя, что здесь можно распараллелить?   -  person Muzaaya Joshua    schedule 13.12.2012
comment
Я тоже думал об этом, но пока я был бы рад, если бы алгоритм работал в целом (имеется в виду с оперативной памятью менее 48 ГБ). Я думаю, что процесс построения квадратов можно было бы распараллелить, но я не совсем уверен, как избежать двойных вычислений.   -  person soupdiver    schedule 13.12.2012


Ответы (2)


Эрланг не ленив, так что

magicSquare(N, Value) ->
    Squares = buildSquare(N, makeRows(N, N*N, Value, N)),

пытается построить список из всех 3 121 348 608 возможных комбинаций четырех строк, каждая из которых в сумме дает 34, используя все числа от 1 до 16 (включительно) между ними при вызове с аргументами 4 и 34.

Даже если бы каждый квадрат занимал всего 16 байт (по одному на каждую ячейку), для этого потребовалось бы около 48 ГБ памяти, не считая накладных расходов на список.

Ваш алгоритм будет работать, хотя и медленно, на ленивом языке. Но в неленивом языке вам нужно обрезать больше и раньше или выбрать совершенно другой алгоритм.

Вы можете проверить, есть ли у вертикалей и диагоналей шанс суммироваться с целевым значением уже в buildSquare, что может привести к достаточно низкому требованию к памяти, чтобы оно поместилось в память для магического квадрата 4 × 4 (но я менее чем убежден ). Если вы построите только сетки (N-1)×N и вычислите последнюю строку из вертикальных сумм, это уменьшит размер Squares еще на один коэффициент N!, у которого больше шансов разместиться в памяти (для N == 4, а не для больших N) вместе с предшествующая обрезка.

Но вы должны перестроить свой алгоритм, чтобы использовать ограничения как можно раньше. Допустим, вы проверяете первую строку 1 2 15 16. Тогда вы знаете, что три числа под 1 в левом столбце и три оставшихся числа на главной диагонали должны в сумме давать 33. Итак, вам нужен набор из шести чисел, выбранных из { 3,4,5,6,7,8,9,10,11,12,13,14} в сумме до 66. Вариантов не так много. из этих шести чисел, поскольку шесть самых больших из них в сумме дают только 69, так что у вас есть

6, 10, 11, 12, 13, 14
7,  9, 11, 12, 13, 14
8,  9, 10, 12, 13, 14

всего три возможности. Два числа в нижних углах также ограничены правым столбцом и основной северо-восточной диагональю. Совместное рассмотрение этих ограничений еще больше сужает пространство поиска.

Если вы рассматриваете возможные квадраты последовательно, один верхний ряд за другим, и не сохраняете кандидатов [вы можете хранить магические квадраты 4×4, их не так уж много], вы можете найти все магические квадраты в маленьких память, и если вы хорошо справляетесь с ограничениями, даже относительно быстро.

person Daniel Fischer    schedule 13.12.2012
comment
Итак, мне нужно каким-то образом объединить buildSquare и makeRows в одну функцию, чтобы я мог гораздо раньше проверить, имеет ли квадрат смысл или нет? - person soupdiver; 13.12.2012
comment
Нет, вы можете иметь makeRows отдельно как есть (хотя было бы эффективнее передать ему список допустимых номеров, чтобы ни один номер не использовался более одного раза с самого начала), но вы должны делать больше и раньше проверять buildSquare. N(ebenbei) B(emerkt), вы перепутали X и L в одном месте в makeRows. - person Daniel Fischer; 13.12.2012
comment
Вы рисуете X из рекурсивного вызова makeRows, а L из seq(1,n), но в результате ставите [X|L], так что список перед номером (ура, для статической типизации, что бы поймать ее во время компиляции). В одном из двух мест следует поменять местами X и L. - person Daniel Fischer; 13.12.2012
comment
хорошо, я добавил некоторые изменения в свой пост. Как вы думаете, что из этого может получиться? - person soupdiver; 13.12.2012
comment
Ради интереса я полагаю, что общая проблема магических квадратов является NP-полной, что означает (грубо говоря), что все алгоритмические решения в общем случае потребуют экспоненциального увеличения памяти или времени (или того и другого) с относительно размера входа проблемы. - person Jr0; 14.12.2012

У меня есть направление, которое может оказаться полезным. У меня почти это работает, но я не смогу тратить на это время в течение следующих нескольких дней.

Во-первых, я считаю, что эта проблема является NP-Complete, что указывает на то, что вы собираетесь использовать экспоненциальную память или время, поскольку размер ввода увеличивается линейно.

В любом случае, это был мой подход:

  1. Если ваш магический квадрат включает числа 1..N, вы создадите все перестановки для этих N чисел. В конце концов, magicSquare(3,15) будет подмножеством всех возможных перестановок 1..15.

  2. Хитрость заключается в удалении каждой перестановки по мере ее создания, если сумма всех строк, которые она представляет, не равна магическому числу. Таким образом, вы не сохраняете все перестановки, а только те, которые очень перспективны, тем самым избегая экспоненциальной памяти (но не экспоненциального времени). Другими словами, параллельно с созданием каждой перестановки сохраняйте ее только в том случае, если она может быть магическим квадратом. Я использовал понимание списка для создания перестановок с квалификатором в генераторе, который провел тест, чтобы убедиться, что все строки суммируются правильно.

  3. Моя тестовая функция приняла параметр, указывающий длину строки (3 в данном случае), и смогла проверить перестановку [8,1,6,3,5,7,4,9,2] и определить, что каждая строка ( каждый подсписок 1-3, 4-6,7-9 в сумме дает 15.
  4. после получения перестановок, где по крайней мере каждая строка суммируется с магическим числом, затем отфильтруйте остальные критерии MN.

Между прочим, убедитесь, что ваш алгоритм создания перестановок рекурсивен по хвосту.

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

Надеюсь, это поможет.

person Jr0    schedule 23.12.2012
comment
Я думаю, что уже делаю это... makeRows(Fields, Numbers, Value, TargetLength) -> [ [X|L] || L <- makeRows(Fields-1, Numbers, Value, TargetLength), X <- lists:seq(1,Numbers), checkRow([X|L], TargetLength, Value)]. уже проверяет каждую новую сгенерированную строку, которую можно использовать. Проблема в том, что buildSquare я просто недостаточно рано определяю, может ли квадрат быть действительным. - person soupdiver; 24.12.2012