【问题标题】:Too much memory consumption when generating magic squares in erlang - Need help for optimization在erlang中生成幻方时内存消耗过多 - 需要优化帮助
【发布时间】:2012-12-13 09:02:05
【问题描述】:

对于大学,我必须实现一种算法,该算法可以为给定的边长和特定的总和创建所有可能的幻方。对于 n=3,算法按预期工作。但是当一段时间后为 n=4 生成所有幻方时,我的内存不足。任务描述中已经提到了这个问题。我已经尝试优化 a 代码,但它仍然无法正常工作。所以我希望有人能给我一些建议。

我的基本想法是:首先我生成所有可能的行,我可以使用给定的数字,然后我尝试以一种完全满足幻方限制的方式组合这些行。这是通过回溯发生的。我认为问题在于函数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).

【问题讨论】:

  • 是的,只要在我认为的代码男孩中向下滚动即可。
  • 我们可以在这里添加一些并发吗? Erlang ninjas,这里可以并行化什么?
  • 我也想过这个问题,但现在我会很高兴算法能正常工作(意味着内存小于 48GB)。我认为构建正方形的过程可以并行化,但我不太确定如何避免重复计算

标签: memory optimization erlang magic-square


【解决方案1】:

嗯,erlang 并不懒惰,所以

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

当使用参数 4 和 34 调用时,尝试使用它们之间从 1 到 16(含)的所有数字来构建四行的所有 3121348608 种可能组合的列表,每行总和为 34。

即使每个方格只占用 16 个字节(每个单元格一个字节),这也需要大约 48GB 的​​内存,这还不包括列表开销。

您的算法可以在懒惰的语言中运行——尽管速度很慢。但在非惰性语言中,您需要更多更早地进行修剪,或者选择完全不同的算法。

您可以测试垂直线和对角线是否有机会与 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 方块,它们不会太多],你可以找到所有魔法小内存中的正方形,如果您以良好的方式处理约束,甚至相对较快。

【讨论】:

  • 好的,所以我必须以某种方式将 buildSquare 和 makeRows 组合在一个函数中,以便我可以更早地检查 square 是否有意义?
  • 不,您可以将 makeRows 按原样分开(尽管将符合条件的数字列表传递给它会更有效,这样从一开始就不会多次使用数字),但是您必须做更多和更早的检查buildSquare。 N(ebenbei) B(emerkt),你把XL混在了makeRows的一处。
  • 你从对makeRows的递归调用中抽取X,从seq(1,n)中抽取L,但是在结果中你放入了[X|L],所以在数字之前的列表(耶对于静态类型,那会在编译时捕获它)。在两个地方之一,你应该交换XL
  • 好的,我在帖子中添加了一些修改。你认为这能解决什么问题?
  • 只是为了兴趣,我相信一般的幻方问题是 NP 完全的意思(松散地说)一般情况的所有算法解决方案都需要呈指数增长内存或时间(或两者)与问题输入的大小有关。
【解决方案2】:

我有一个可能会有所帮助的方向。我几乎让它工作了,但在接下来的几天里将无法花任何时间在它上面。

首先,我相信这个问题是 NP 完全问题,这表明随着输入大小线性增加,您将使用指数内存或时间。

无论如何,这是我的方法:

  1. 如果您的幻方包含数字 1..N,您将为这 N 个数字创建所有排列。毕竟,magicSquare(3,15) 将是 1..15 的所有可能排列的子集

  2. 诀窍是在生成时删除每个排列,如果它所代表的所有行的总和不等于幻数。通过这种方式,您不会存储所有排列,只存储那些非常有希望的排列,从而避免指数记忆(但不是指数时间)。换句话说,与生成每个排列一致,只有当它可能是一个幻方时才保存它。我使用列表推导在生成器上创建了带有限定符的排列,该生成器执行了 test 以确保所有行正确求和

  3. 我的测试函数采用了一个指示行长的参数(在本例中为 3),并且能够检查 [8,1,6,3,5,7,4,9,2] 的排列并确定每行(每个子列表 1-3、4-6、7-9 总和为 15。
  4. 在获得至少每行总和为幻数的排列后,然后过滤其余的 MN 标准。

顺便说一句,请确保您创建排列的算法是尾递归的。

再一次,这似乎对我有用(除非它不是;)),但我已经离开我的电脑几天了。

希望这会有所帮助。

【讨论】:

  • 我想我已经这样做了...makeRows(Fields, Numbers, Value, TargetLength) -&gt; [ [X|L] || L &lt;- makeRows(Fields-1, Numbers, Value, TargetLength), X &lt;- lists:seq(1,Numbers), checkRow([X|L], TargetLength, Value)]. 已经检查了可以使用的每个新生成的行。问题是buildSquare我只是没有足够早地检测到正方形是否可以有效
猜你喜欢
  • 1970-01-01
  • 2021-08-26
  • 2012-03-02
  • 2020-01-27
  • 1970-01-01
  • 1970-01-01
  • 2023-01-27
相关资源
最近更新 更多