如何在Erlang中将列表列表相乘?

时间:2017-10-20 16:26:23

标签: matrix erlang matrix-multiplication

我正在尝试将两个矩阵放在一起,我有一些代码可以生成矩阵并使用列表列表显示它们。但我无法弄清楚如何将我所拥有的两个矩阵相乘。

到目前为止,这是我的代码:

-module(main).
-export([main/0]).
-export([matrix/2, random/2]).

main() ->
    MatrixA = random(3, 100),
    MatrixB = random(3, 100),

    main:matrix(MatrixA, MatrixB).

matrix(MatrixA, MatrixB) ->

    Print = fun(X) -> io:format("~w, ", [X]) end,

    io:fwrite("Matrix A: "),
    lists:foreach(Print, MatrixA),
    io:fwrite("\n\nMatrix B: "),
    lists:foreach(Print, MatrixB),
    io:fwrite("\n\nMatrix C: "),
    io:fwrite("\n").

random(Size, MaxValue) ->
    random(1, 1, Size, MaxValue, [], []).

-define(VALUE(X, Y), value(X, Y, MaxValue)).

value(_, _, MaxValue) ->
    rand:uniform(MaxValue).

random(Size, Size, Size, MaxValue, Row, Acc) ->
    [[?VALUE(Size, Size) | Row] | Acc];

random(Size, Y, Size, MaxValue, Row, Acc) ->
    random(1, Y+1, Size, MaxValue, [], [[?VALUE(Size, Y) | Row] | Acc]);

random(X, Y, Size, MaxValue, Row, Acc) ->
    random(X+1, Y, Size, MaxValue, [?VALUE(X, Y) | Row], Acc).

另外,有没有办法改变这段代码,以便矩阵中的数字是十进制值?

1 个答案:

答案 0 :(得分:2)

注意:StackOverflow并不是解决作业类型问题的真正地方,但矩阵乘法很有意思,因为它的强大性质对于我们在功能程序中几乎所有事情的方式都非常重要,所以它可以是一种不同方法的展示。我认为将下面的代码作为家庭作业提交几乎是不可能的(特别是因为它现在是indexed在我自己的网站上,大多数学校和抄袭探测器都将其编入索引,但我认为如果你冥想这段代码,你就有机会成长为一名程序员,并把它修改为使它成为你自己的。这对每个人都有好处。

关于列表的快速注释

为了简单地将列表与列表相乘,可以使用几种不同的方法。列表理解很常见:

multiply(Scalar, Row) ->
    [Scalar * Value || Value <- Row].

还用于将列表的排列与列表相乘:

multiply(ListA, ListB) ->
    [A * B || A <- ListA, B <- ListB].

这可以帮助你解决问题,但并不是你想要的。您需要更深入地实现quite a few rules to matrix multiplication,如果我们不想编写完全数学上有缺陷的模块,我们应该至少包含一些验证输入所需的输入检查或至少崩溃显然是错误的输入。

一个成熟的例子

我最初开始解释为什么列表操作按照他们的方式工作,但我最终编写了一个完整的天真矩阵乘法模块来说明这一点,所以... meh,无论如何。

我使用了列表操作,我认为它们非常明显(并且范围内值很方便)和其他地方的显式递归(因为在某些操作中有一些状态浮动,尤其是旋转)。

仔细阅读

要特别注意random/2random/3函数的工作方式,并将它们与您的代码进行比较。下面的版本很多更容易理解,这是好东西。尽可能地尝试不让你的代码的未来读者在他们读取单行时在他们的大脑中保持一堆正在进行的状态,比如在另一个列表操作中具有列表操作或生成器 - 除非它是非常简单正在进行的转换,并且语句比它们被分开时更具表现力。

此外,make use of labels可让您的代码尽可能自我记录。

作为关于样式的最后说明,请注意此处的样式具有typespecs(适用于Dialyzer - 此处a primer - 了解此),edoc注释并以符合zuuid使用的样式的方式编写,即a project written specifically to serve as a platform for discovering sound style and an example of the result

%%% @doc
%%% A naive matrix generation, rotation and multiplication module.
%%% It doesn't concern itself with much checking, so input dimensions must be known
%%% prior to calling any of these functions lest you receive some weird results back,
%%% as most of these functions do not crash on input that go against the rules of
%%% matrix multiplication.
%%%
%%% All functions crash on obviously bad values.
%%% @end 

-module(naive_matrix).
-export([random/2, random/3, rotate/1, multiply/2]).

-type matrix() :: [[number()]].


-spec random(Size, MaxValue) -> Matrix
    when Size     :: pos_integer(),
         MaxValue :: pos_integer(),
         Matrix   :: matrix().
%% @doc
%% Generate a square matrix of dimensions {Size, Size} populated with random
%% integer values inclusive of 1..MaxValue.

random(Size, MaxValue) when Size > 0, MaxValue > 0 ->
    random(Size, Size, MaxValue).


-spec random(X, Y, MaxValue) -> Matrix
    when X        :: pos_integer(),
         Y        :: pos_integer(),
         MaxValue :: pos_integer(),
         Matrix   :: matrix().
%% @doc
%% Generate a matrix of dimensions {X, Y} populated with random integer values
%% inclusive 1..MaxValue.

random(X, Y, MaxValue) when X > 0, Y > 0, MaxValue > 0 ->
    Columns = lists:duplicate(X, []),
    Populate = fun(Col) -> row(Y, MaxValue, Col) end,
    lists:map(Populate, Columns).


-spec row(Size, MaxValue, Acc) -> NewAcc
    when Size     :: non_neg_integer(),
         MaxValue :: pos_integer(),
         Acc      :: [pos_integer()],
         NewAcc   :: [pos_integer()].
%% @private
%% Generate a single row of random integers.

row(0, _, Acc) ->
    Acc;
row(Size, MaxValue, Acc) ->
    row(Size - 1, MaxValue, [rand:uniform(MaxValue) | Acc]).


-spec rotate(matrix()) -> matrix().
%% @doc
%% Takes a matrix of {X, Y} size and rotates it left, returning a matrix of {Y, X} size.

rotate(Matrix) ->
    rotate(Matrix, [], [], []).


-spec rotate(Matrix, Rem, Current, Acc) -> Rotated
    when Matrix  :: matrix(),
         Rem     :: [[number()]],
         Current :: [number()],
         Acc     :: matrix(),
         Rotated :: matrix().
%% @private
%% Iterates doubly over a matrix, packing the diminished remainder into Rem and
%% packing the current row into Current. This is naive, in that it assumes an
%% even matrix of dimentions {X, Y}, and will return one of dimentions {Y, X}
%% based on the length of the first row, regardless whether the input was actually
%% even.

rotate([[] | _], [], [], Acc) ->
    Acc;
rotate([], Rem, Current, Acc) ->
    NewRem = lists:reverse(Rem),
    NewCurrent = lists:reverse(Current),
    rotate(NewRem, [], [], [NewCurrent | Acc]);
rotate([[V | Vs] | Rows], Rem, Current, Acc) ->
    rotate(Rows, [Vs | Rem], [V | Current], Acc).


-spec multiply(ValueA, ValueB) -> Product
    when ValueA  :: number() | matrix(),
         ValueB  :: number() | matrix(),
         Product :: number() | matrix().
%% @doc
%% Accept any legal combination of scalar and matrix values to be multiplied.
%% The correct operation will be chosen based on input values.

multiply(A, B) when is_number(A), is_number(B) ->
    A * B;
multiply(A, B) when is_number(A), is_list(B) ->
    multiply_scalar(A, B);
multiply(A, B) when is_list(A), is_list(B) ->
    multiply_matrix(A, B).


-spec multiply_scalar(A, B) -> Product
    when A       :: number(),
         B       :: matrix(),
         Product :: matrix().
%% @private
%% Simple scalar multiplication of a matrix.

multiply_scalar(A, B) ->
    multiply_scalar(A, B, []).


-spec multiply_scalar(A, B, Acc) -> Product
    when A       :: number(),
         B       :: matrix(),
         Acc     :: matrix(),
         Product :: matrix().
%% @private
%% Scalar multiplication is implemented here as an explicit recursion over
%% a list of lists, each element of which is subjected to a map operation.

multiply_scalar(A, [B | Bs], Acc) ->
    Row = lists:map(fun(N) -> A * N end, B),
    multiply_scalar(A, Bs, [Row | Acc]);
multiply_scalar(_, [], Acc) ->
    lists:reverse(Acc).


-spec multiply_matrix(A, B) -> Product
    when A       :: matrix(),
         B       :: matrix(),
         Product :: matrix().
%% @doc
%% Multiply two matrices together according to the matrix multiplication rules.
%% This function does not check that the inputs are actually proper (regular)
%% matrices, but does check that the input row/column lengths are compatible.

multiply_matrix(A = [R | _], B) when length(R) == length(B) ->
    multiply_matrix(A, rotate(B), []).


-spec multiply_matrix(A, B, Acc) -> Product
    when A       :: matrix(),
         B       :: matrix(),
         Acc     :: matrix(),
         Product :: matrix().
%% @private
%% Iterate a row multiplication operation of each row of A over matrix B until
%% A is exhausted.

multiply_matrix([A | As], B, Acc) ->
    Prod = multiply_row(A, B, []),
    multiply_matrix(As, B, [Prod | Acc]);
multiply_matrix([], _, Acc) ->
    lists:reverse(Acc).


-spec multiply_row(Row, B, Acc) -> Product
    when Row     :: [number()],
         B       :: matrix(),
         Acc     :: matrix(),
         Product :: [number()].
%% @private
%% Multiply each row of matrix B by the input Row, returning the list of resulting sums.

multiply_row(Row, [B | Bs], Acc) ->
    ZipProd = lists:zipwith(fun(X, Y) -> X * Y end, Row, B),
    Sum = lists:sum(ZipProd),
    multiply_row(Row, Bs, [Sum | Acc]);
multiply_row(_, [], Acc) ->
    Acc.

请注意,上面的代码不会使您的矩阵成为浮点值。有一个名为float/1的Erlang BIF,它接受任何数字并返回一个浮点数 - 所以当然,上面的代码可以做到这一点,你可以做任何你想做的事情,而不用大惊小怪。