注意: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,如果我们不想编写一个完全数学上有缺陷的模块,我们可能应该至少包含一些必要的输入检查验证输入或至少崩溃明显错误的输入。
一个完整的例子
我最初打算解释为什么列表运算会以它们的方式工作,但最后我编写了一个完整的简单矩阵乘法模块来说明这一点,所以……嗯,随便。
我在我认为它们非常明显的地方使用了列表操作(并且对范围内的值进行排序很方便),并在其他地方使用了显式递归(因为在其中一些操作中存在一些状态浮动,尤其是旋转)。
仔细阅读
特别注意random/2 和random/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,它接受任何数字并返回一个浮点数——因此,当然,可以让上面的代码在这方面做任何你想做的事情,而无需大惊小怪。