erlang university.erl
university.erl
#!/usr/bin/env escript
% Run in shell with
% escript university.erl
-module(university).
-export([average/2, average/3, professor_annual_salary/3, main/1]).
average(P1, P2) -> (P1 + P2) / 2.
average(P1, P2, P3) -> (P1 + P2 + P3) / 3.
professor_base_salary(adjunct, "US$") -> 20000;
professor_base_salary(adjunct, "R$") -> 30000;
professor_base_salary(assistant, Currency) -> 3* professor_base_salary(adjunct, Currency);
professor_base_salary(associate, Currency) -> 5* professor_base_salary(adjunct, Currency);
professor_base_salary(full, Currency) -> 10* professor_base_salary(adjunct, Currency).
professor_annual_salary(Name, Position, Currency) ->
{Name, professor_base_salary(Position, Currency), Currency}.
main(_) ->
io:format("Salaries at the University of Some Place (USP)~n~n"),
{Name1, Salary1, Currency1} = professor_annual_salary("John", full, "US$"),
io:format("~s receives an annual salary of ~s ~w.~n~n", [Name1,Currency1, Salary1]),
{Name2, Salary2, Currency2} = professor_annual_salary("João", associate, "R$"),
io:format("~s receives an annual salary of ~s ~w.~n~n", [Name2,Currency2, Salary2]).
erlang nif_test.erl
nif_test.erl
-module (nif_test).
-export ([create/1, read/1, add/1 ,cb/1,listen/1]).
-on_load(init/0).
init()->
erlang:load_nif("./nif_test", 0).
listen(t) ->
cb(t),
receive
Msg -> Msg
after 10000 ->
erlang:errot(timeout)
end.
create(_) ->
not_loaded(?LINE).
add(_) ->
not_loaded(?LINE).
read(_) ->
not_loaded(?LINE).
cb(_) ->
not_loaded(?LINE).
not_loaded(Line) ->
exit({not_loaded, [{module, ?MODULE}, {line, Line}]}).
erlang 在Y分钟学习Erlang(https://learnxinyminutes.com)
在Y分钟学习Erlang(https://learnxinyminutes.com)
learnerlang-cn.erl
% 百分比符号标明注释的开始。
%% 两个符号通常用于注释函数。
%%% 三个符号通常用于注释模块。
% Erlang 里使用三种标点符号:
% 逗号 (`,`) 分隔函数调用中的参数、数据构建和模式。
% 句号 (`.`) (后跟空格)分隔函数和 shell 中的表达式。
% 分号 (`;`) 分隔语句。以下环境中使用语句:
% 函数定义和`case`、`if`、`try..catch`、`receive`表达式。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 1. 变量和模式匹配
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Num = 42. % 变量必须以大写字母开头。
% Erlang 的变量只能赋值一次。如果给变量赋不同的值,会导致错误:
Num = 43. % ** exception error: no match of right hand side value 43
% 大多数语言中`=`表示赋值语句,在Erlang中,则表示模式匹配。
% `Lhs = Rhs`实际上意味着:
% 演算右边(Rhs), 将结果与左边的模式匹配。
Num = 7 * 6.
% 浮点数
Pi = 3.14159.
% Atoms 用于表示非数字的常量。
% Atom 以小写字母开始,包含字母、数字、`_`和`@`。
Hello = hello.
OtherNode = example@node.
% Atom 中如果包含特殊字符,可以用单引号括起。
AtomWithSpace = 'some atom with space'.
% Erlang 的元组类似 C 的 struct.
Point = {point, 10, 45}.
% 使用模式匹配操作符`=`获取元组的值。
{point, X, Y} = Point. % X = 10, Y = 45
% 我们可以使用`_`存放我们不感兴趣的变量。
% `_`被称为匿名变量。和其他变量不同,
% 同一个模式中的多个`_`变量不必绑定到相同的值。
Person = {person, {name, {first, joe}, {last, armstrong}}, {footsize, 42}}.
{_, {_, {_, Who}, _}, _} = Person. % Who = joe
% 列表使用方括号,元素间使用逗号分隔。
% 列表的元素可以是任意类型。
% 列表的第一个元素称为列表的 head,其余元素称为列表的 tail。
ThingsToBuy = [{apples, 10}, {pears, 6}, {milk, 3}].
% 若`T`是一个列表,那么`[H|T]`同样是一个列表,head为`H`,tail为`T`.
% `|`分隔列表的 head 和 tail.
% `[]`是空列表。
% 我们可以使用模式匹配操作来抽取列表中的元素。
% 如果我们有一个非空的列表`L`,那么`[X|Y] = L`则
% 抽取 L 的 head 至 X,tail 至 Y (X、Y需为未定义的变量)。
[FirstThing|OtherThingsToBuy] = ThingsToBuy.
% FirstThing = {apples, 10}
% OtherThingsToBuy = {pears, 6}, {milk, 3}
% Erlang 中的字符串其实是由整数组成的数组。字符串使用双引号。
Name = "Hello".
[72, 101, 108, 108, 111] = "Hello".
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 2. 循序编程
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Module 是 Erlang 代码的基本单位。我们编写的所有函数都储存在 module 中。
% Module 存储在后缀为 `.erl` 的文件中。
% Module 必须事先编译。编译好的 module 以 `.beam` 结尾。
-module(geometry).
-export([area/1]). % module 对外暴露的函数列表
% `area`函数包含两个分句,分句间以分号相隔。
% 最后一个分句以句号加换行结尾。
% 每个分句由头、体两部门组成。
% 头部包含函数名称和用括号括起的模式,
% 体部包含一系列表达式,如果头部的模式和调用时的参数匹配,这些表达式会被演算。
% 模式匹配依照定义时的顺序依次进行。
area({rectangle, Width, Ht}) -> Width * Ht;
area({circle, R}) -> 3.14159 * R * R.
% 编译文件为 geometry.erl.
c(geometry). % {ok,geometry}
% 调用函数时必须使用 module 名和函数名。
geometry:area({rectangle, 10, 5}). % 50
geometry:area({circle, 1.4}). % 6.15752
% 在 Erlang 中,同一模块中,参数数目不同,名字相同的函数是完全不同的函数。
-module(lib_misc).
-export([sum/1]). % 对外暴露的`sum`函数接受一个参数:由整数组成的列表。
sum(L) -> sum(L, 0).
sum([], N) -> N;
sum([H|T], N) -> sum(T, H+N).
% fun 是匿名函数。它们没有名字,不过可以赋值给变量。
Double = fun(X) -> 2*X end. % `Double` 指向匿名函数 #Fun<erl_eval.6.17052888>
Double(2). % 4
% fun 可以作为函数的参数和返回值。
Mult = fun(Times) -> ( fun(X) -> X * Times end ) end.
Triple = Mult(3).
Triple(5). % 15
% 列表解析是创建列表的表达式(不使用fun、map 或 filter)。
% `[F(X) || X <- L]` 表示 "由 `F(X)` 组成的列表,其中 `X` 取自列表 `L`。
L = [1,2,3,4,5].
[2*X || X <- L]. % [2,4,6,8,10]
% 列表解析可以使用生成器,也可以使用过滤器,过滤器用于筛选列表的一部分。
EvenNumbers = [N || N <- [1, 2, 3, 4], N rem 2 == 0]. % [2, 4]
% Guard 是用于增强模式匹配的结构。
% Guard 可用于简单的测试和比较。
% Guard 可用于函数定义的头部,以`when`关键字开头,或者其他可以使用表达式的地方。
max(X, Y) when X > Y -> X;
max(X, Y) -> Y.
% guard 可以由一系列 guard 表达式组成,这些表达式以逗号分隔。
% `GuardExpr1, GuardExpr2, ..., GuardExprN` 为真,当且仅当每个 guard 表达式均为真。
is_cat(A) when is_atom(A), A =:= cat -> true;
is_cat(A) -> false.
is_dog(A) when is_atom(A), A =:= dog -> true;
is_dog(A) -> false.
% guard 序列 `G1; G2; ...; Gn` 为真,当且仅当其中任意一个 guard 表达式为真。
is_pet(A) when is_dog(A); is_cat(A) -> true;
is_pet(A) -> false.
% Record 可以将元组中的元素绑定到特定的名称。
% Record 定义可以包含在 Erlang 源代码中,也可以放在后缀为`.hrl`的文件中(Erlang 源代码中 include 这些文件)。
-record(todo, {
status = reminder, % Default value
who = joe,
text
}).
% 在定义某个 record 之前,我们需要在 shell 中导入 record 的定义。
% 我们可以使用 shell 函数`rr` (read records 的简称)。
rr("records.hrl"). % [todo]
% 创建和更新 record。
X = #todo{}.
% #todo{status = reminder, who = joe, text = undefined}
X1 = #todo{status = urgent, text = "Fix errata in book"}.
% #todo{status = urgent, who = joe, text = "Fix errata in book"}
X2 = X1#todo{status = done}.
% #todo{status = done,who = joe,text = "Fix errata in book"}
% `case` 表达式。
% `filter` 返回由列表`L`中所有满足`P(x)`为真的元素`X`组成的列表。
filter(P, [H|T]) ->
case P(H) of
true -> [H|filter(P, T)];
false -> filter(P, T)
end;
filter(P, []) -> [].
filter(fun(X) -> X rem 2 == 0 end, [1, 2, 3, 4]). % [2, 4]
% `if` 表达式。
max(X, Y) ->
if
X > Y -> X;
X < Y -> Y;
true -> nil;
end.
% 警告: `if` 表达式里至少有一个 guard 为真,否则会触发异常。
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 3. 异常
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% 当遇到内部错误或显式调用时,会触发异常。
% 显式调用包括 `throw(Exception)`, `exit(Exception)` 和
% `erlang:error(Exception)`.
generate_exception(1) -> a;
generate_exception(2) -> throw(a);
generate_exception(3) -> exit(a);
generate_exception(4) -> {'EXIT', a};
generate_exception(5) -> erlang:error(a).
% Erlang 有两种捕获异常的方法。其一是将调用包裹在`try...catch`表达式中。
catcher(N) ->
try generate_exception(N) of
Val -> {N, normal, Val}
catch
throw:X -> {N, caught, thrown, X};
exit:X -> {N, caught, exited, X};
error:X -> {N, caught, error, X}
end.
% 另一种方式是将调用包裹在`catch`表达式中。
% 此时异常会被转化为一个描述错误的元组。
catcher(N) -> catch generate_exception(N).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% 4. 并发
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Erlang 依赖于 actor并发模型。在 Erlang 编写并发程序的三要素:
% 创建进程,发送消息,接收消息
% 启动一个新的进程使用`spawn`函数,接收一个函数作为参数
F = fun() -> 2 + 2 end. % #Fun<erl_eval.20.67289768>
spawn(F). % <0.44.0>
% `spawn` 函数返回一个pid(进程标识符),你可以使用pid向进程发送消息。
% 使用 `!` 操作符发送消息。
% 我们需要在进程内接收消息,要用到 `receive` 机制。
-module(caculateGeometry).
-compile(export_all).
caculateAera() ->
receive
{rectangle, W, H} ->
W * H;
{circle, R} ->
3.14 * R * R;
_ ->
io:format("We can only caculate area of rectangles or circles.")
end.
% 编译这个模块,在 shell 中创建一个进程,并执行 `caculateArea` 函数。
c(caculateGeometry).
CaculateAera = spawn(caculateGeometry, caculateAera, []).
CaculateAera ! {circle, 2}. % 12.56000000000000049738
% shell也是一个进程(process), 你可以使用`self`获取当前 pid
self(). % <0.41.0>
erlang Erlang中的并发编程 - 第1周分配
Erlang中的并发编程 - 第1周分配
ass_wk2_conc.erl
%% Based on code from
%% Erlang Programming
%% Francecso Cesarini and Simon Thompson
%% O'Reilly, 2008
%% http://oreilly.com/catalog/9780596518189/
%% http://www.erlangprogramming.org/
%% (c) Francesco Cesarini and Simon Thompson
-module(frequency_sup).
-export([start/0,allocate/0,deallocate/1,stop/0,client/3,start_client/1]).
-export([init/2,super/2]).
% Launches the supervisor process with the specified MFA and registered name
% to which {M, F, A} is to be associated to.
super({M, F, A}, Name) ->
% Add a reference to the supervisor's PID so that the child has a reference
% to its parent, in order to be able to communicate with it to send its
% state, as well as to be able to determine whether the supervisor was
% terminated or one of the clients was terminated.
State = [self() | A],
% Trap exits.
process_flag(trap_exit, true),
io:format("Starting system with {M, F, A} ~p.~n", [{M, F, State}]),
% Spawn and register the MFA used to manage the main process. Again, note that
% we have injected the supervisor PID into the arguments.
register(Name, ChildPid = spawn_link(M, F, State)),
super_loop({M, F, State}, Name, ChildPid).
% The loop which manages the lifetime of the process that is spawned using the
% {M, F, A} specified above. We also maintain the child PID, so that messages
% tagged with {state, ...} coming from the child PID are identified.
super_loop({M, F, State}, Name, ChildPid) ->
receive
{'EXIT', _Pid, _Reason} ->
io:format("Restarting system.~n"),
register(Name, NewChildPid = spawn_link(M, F, State)),
super_loop({M, F, State}, Name, NewChildPid);
{state, ChildPid, NewFrequencies} ->
io:format("Updating frequencies in supervisor ~p.~n", [NewFrequencies]),
super_loop({M, F, [self(), NewFrequencies]}, Name, ChildPid)
end.
%% These are the start functions used to create and
%% initialize the server.
start() ->
% Get the list of hard-coded frequencies to pass to the init function, which
% is presented as a {M, F, A} below. Note that the process is will also be
% registered under the name 'frequency'.
Frequencies = {get_frequencies(), []},
register(super, spawn(frequency_sup, super,
[{frequency_sup, init, [Frequencies]}, frequency])).
% The main function includes also the supervisor PID, in addition to the
% frequencies. This serves two functions:
% 1. The main process will be able to send its state also to the supervisor,
% so that if it crashes, the supervisor knows to start it from the last
% known state.
% 2. This process is set to trap EXITs to deal with client deaths. However,
% since the supervisor spawns_links this process, a death of the supervisor
% would also send a message to this process in the form of KILLED. We need
% to identify between process death messages coming from the supervisor and
% the connected clients, and we do this by pattern-matching the EXIT
% messages using the SuperPid passed to the init/2 function.
init(SuperPid, Frequencies) ->
process_flag(trap_exit, true),
loop(SuperPid, Frequencies).
% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].
%% The Main Loop
% The main frequency server loop. Note that in addition to the frequencies, it
% also takes the supervisor PID. The updated state that is generated after
% processing each allocate/deallocate/client death messages is relayed to the
% supervisor, so that in case of the frequency server's death, this data is can
% be used by the supervisor to restart the frequency server in the last known
% state.
loop(SuperPid, Frequencies) ->
% Print list of frequencies.
io:format("[SERVER] List of frequencies free/assigned ~p.~n", [Frequencies]),
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
Pid ! {reply, Reply},
super ! {state, self(), NewFrequencies},
loop(SuperPid, NewFrequencies);
{request, Pid , {deallocate, Freq}} ->
NewFrequencies = deallocate(Frequencies, Freq),
Pid ! {reply, ok},
super ! {state, self(), NewFrequencies},
loop(SuperPid, NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stopped},
exit(stop);
{'EXIT', SuperPid, _Reason} ->
exit(stopped_sup);
{'EXIT', Pid, _Reason} ->
NewFrequencies = exited(Frequencies, Pid),
super ! {state, self(), NewFrequencies},
loop(SuperPid, NewFrequencies)
end.
%% Functional interface
allocate() ->
frequency ! {request, self(), allocate},
receive
{reply, Reply} -> Reply
end.
deallocate(Freq) ->
frequency ! {request, self(), {deallocate, Freq}},
receive
{reply, Reply} -> Reply
end.
stop() ->
frequency ! {request, self(), stop},
receive
{reply, Reply} -> Reply
end.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
link(Pid), %%% ADDED
io:format("[Server] Allocated new frequency ~p to client ~p.~n", [Freq, Pid]),
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, Freq) ->
{value,{Freq,Pid}} = lists:keysearch(Freq,1,Allocated), %%% ADDED
unlink(Pid), %%% ADDED
NewAllocated=lists:keydelete(Freq, 1, Allocated),
io:format("[Server] Deallocated existing frequency ~p from client ~p.~n", [Freq, Pid]),
{[Freq|Free], NewAllocated}.
exited({Free, Allocated}, Pid) -> %%% FUNCTION ADDED
case lists:keysearch(Pid,2,Allocated) of
{value,{Freq,Pid}} ->
NewAllocated = lists:keydelete(Freq,1,Allocated),
io:format("[Server] Reclaimed frequency ~p to client ~p.~n", [Freq, Pid]),
{[Freq|Free],NewAllocated};
false ->
{Free,Allocated}
end.
% Starts the client with the specified mode. The client process is set to
% trap EXITs so that when the frequency server process dies, it suspends itself.
% Also, this allows us to handle a stop command to issued to the frequency
% server by gracefully terminating all the clients.
% The client operates in one of two modes:
% die: the client makes one allocation and then dies normally. This triggers the
% frequency server to reclaim the allocated frequency number.
% normal: The client alternates between allocating a new frequency number and
% deallocating it.
start_client(Type) ->
spawn(fun() -> process_flag(trap_exit, true), client(Type, null, whereis(frequency)) end).
% Modes: die, normal
client(Type, AllocatedFreq, ServerPid) ->
% Get PID for logging purposes.
Pid = self(),
receive
{'EXIT', ServerPid, killed} ->
% Frequency server is dead - suspend myself.
io:format("[CLIENT ~p] Frequency server ~p is dead...suspending till wakeup call.~n", [Pid, ServerPid]),
receive
{NewPid, wakeup} -> client(Type, AllocatedFreq, NewPid)
end;
{'EXIT', ServerPid, stop} ->
% Normal stop signal from system.
io:format("[CLIENT ~p] Putting client to sleep.~n", [Pid]),
exit(stop)
after 0 -> ok
end,
case Type of
die ->
% Allocate, wait and die. If we do not match the pattern (i.e. all
% frequencies are depleated, we die abnormally), othewise we terminate
% normally.
{ok, Freq} = allocate(),
io:format("[CLIENT ~p] Allocated ~p and dying normally.~n", [Pid, Freq]),
timer:sleep(3000);
normal ->
% Default behaviour - allocate and hold frequency and then deallocate.
% If we do not match the pattern (i.e. all frequencies are depleated,
% we die abnormally).
case AllocatedFreq of
null ->
% We are not allocated a frequency, so let's acquire one.
{ok, Freq} = allocate(),
io:format("[CLIENT ~p] Allocated ~p and will release it soon.~n", [Pid, Freq]),
timer:sleep(1000 + rand:uniform(5000)),
client(Type, Freq, ServerPid);
_ ->
% We are allocated a frequency, so let's relinquish it.
deallocate(AllocatedFreq),
client(Type, null, ServerPid)
end
end.
erlang Erlang中的并发编程 - 第1周分配
Erlang中的并发编程 - 第1周分配
ass_wk1_conc.erl
%% Based on code from
%% Erlang Programming
%% Francecso Cesarini and Simon Thompson
%% O'Reilly, 2008
%% http://oreilly.com/catalog/9780596518189/
%% http://www.erlangprogramming.org/
%% (c) Francesco Cesarini and Simon Thompson
-module(frequency2).
-export([start/0,allocate/0,deallocate/1,stop/0,clear/0]).
-export([init/0]).
%% These are the start functions used to create and
%% initialize the server.
start() ->
register(frequency,
spawn(frequency2, init, [])).
init() ->
Frequencies = {get_frequencies(), []},
loop(Frequencies).
% Hard Coded
get_frequencies() -> [10,11,12,13,14,15].
%% The Main Loop
loop(Frequencies) ->
receive
{request, Pid, allocate} ->
{NewFrequencies, Reply} = allocate(Frequencies, Pid),
% Simulate overload.
timer:sleep(2000),
Pid ! {reply, Reply},
loop(NewFrequencies);
{request, Pid , {deallocate, Freq}} ->
NewFrequencies = deallocate(Frequencies, Freq),
% Simulate overload.
timer:sleep(2000),
Pid ! {reply, ok},
loop(NewFrequencies);
{request, Pid, stop} ->
Pid ! {reply, stopped}
end.
%% Functional interface
allocate() ->
% Clear any unnecessary messages that might be sent in response to timed
% out requests, so that we will only have the reply of the new request that
% is about to be sent to the server.
clear(),
frequency ! {request, self(), allocate},
receive
{reply, Reply} -> Reply
after 1000 -> timeout
end.
deallocate(Freq) ->
% Clear any unnecessary messages that might be sent in response to timed
% out requests, so that we will only have the reply of the new request that
% is about to be sent to the server.
clear(),
frequency ! {request, self(), {deallocate, Freq}},
receive
{reply, Reply} -> Reply
after 1000 -> timeout
end.
stop() ->
frequency ! {request, self(), stop},
receive
{reply, Reply} -> Reply
end.
%% The Internal Help Functions used to allocate and
%% deallocate frequencies.
allocate({[], Allocated}, _Pid) ->
{{[], Allocated}, {error, no_frequency}};
allocate({[Freq|Free], Allocated}, Pid) ->
{{Free, [{Freq, Pid}|Allocated]}, {ok, Freq}}.
deallocate({Free, Allocated}, Freq) ->
NewAllocated=lists:keydelete(Freq, 1, Allocated),
{[Freq|Free], NewAllocated}.
% Clears the messages from the mailbox. It avoids blocking when there are no
% messages by using a timeout of 0. And it also avoids looping forever by
% not calling itself again once the 'after 0' clause is taken. Before that,
% the function calls itself indefinitely until the whole mailbox has been
% emptied of all messages. It also prints each message that is chucked out of
% the mailbox.
clear() ->
receive
Msg ->
io:format("Clearing message: ~p~n", [Msg]),
clear()
after 0 -> ok
end.
erlang rps.erl
rps.erl
%%% assignment 3.12 Functional Programming in Erlang, 2017
-module(rps).
-export([play/1,echo/1,play_two/3,rock/1,no_repeat/1,const/1,enum/1,cycle/1,rand/1,val/1,tournament/2]).
-export([type_count/1, least_freq_played/1,most_freq_played/1,rand_strategy/1]).
-created_by("Marcelo Ruiz Camauër").
-include_lib("eunit/include/eunit.hrl").
%
% interactively play against a strategy, provided as argument.
% example:
% rps:play(fun rps:rand/1).
%
play(Strategy) ->
% play interactively until user enters "stop"
io:format("Rock - paper - scissors~n"),
io:format("Play one of rock (r), paper (p), scissors (s), or 'stop'. ~n"),
play(Strategy,[]).
% tail recursive loop for play/1:
play(Strategy,Moves) ->
{ok,P} = io:read("Your play: "),
Play = expand(P),
case Play of
stop ->
io:format("Stopped~n");
_ -> % should have something here to catch illegal input...
OponentPlay = Strategy(Moves),
Result = result(Play,OponentPlay),
io:format("Result: ~p vs ~p, you ~p~n",[Play,OponentPlay,Result]),
play(Strategy,[Play|Moves])
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% auxiliary functions
%
% transform shorthand atoms to expanded form
expand(r) -> rock;
expand(p) -> paper;
expand(s) -> scissors;
expand(X) -> X.
% result of one set of plays
result(rock,rock) -> draw;
result(rock,paper) -> lose;
result(rock,scissors) -> win;
result(paper,rock) -> win;
result(paper,paper) -> draw;
result(paper,scissors) -> lose;
result(scissors,rock) -> lose;
result(scissors,paper) -> win;
result(scissors,scissors) -> draw.
% result of a tournament
tournament(PlaysL,PlaysR) ->
lists:sum(
lists:map(fun outcome/1,
lists:zipwith(fun result/2,PlaysL,PlaysR))).
outcome(win) -> 1;
outcome(lose) -> -1;
outcome(draw) -> 0.
% transform 0, 1, 2 to rock, paper, scissors and vice versa.
enum(0) ->
rock;
enum(1) ->
paper;
enum(2) ->
scissors.
val(rock) ->
0;
val(paper) ->
1;
val(scissors) ->
2.
% give the play which the argument beats.
beats(rock) ->
scissors;
beats(paper) ->
rock;
beats(scissors) ->
paper.
type_count(L) -> % by Callous M.
FilterFn = fun(Type) -> fun(X) when X =:= Type -> true; (_) -> false end end,
R = {_, rock} = {length(lists:filter(FilterFn(rock), L)), rock},
P = {_, paper} = {length(lists:filter(FilterFn(paper), L)), paper},
S = {_, scissors} = {length(lists:filter(FilterFn(scissors), L)), scissors},
{R, P, S}.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% STRATEGIES:
% Add file strategies that when passed a list of previous moves by the opponent, will determine what to play:
%
% * assume that your opponent never repeats herself: if you know this you can make a choice that will never lose;
% * make a random choice each time; you may want to use the random:uniform/1 function so that
% random:uniform(N) returns a random choice of 1,2, … N with equal probability each time;
% * cycles through the three choices in some order;
% * apply an analysis to the previous plays and choose the least frequent, assuming that in the long run your
% opponent will play each choice equally;
% * apply an analysis to the previous plays and choose the most frequent, assuming that in the long run your
% opponent is going to play that choice more often than the others.
%
% We can also define functions that combine strategies and return strategies:
% * Define a strategy that takes a list of strategies and each play chooses a random one to apply.
% * Define a strategy that takes a list of strategies and each play chooses from the list the strategy
% which gets the best result when played against the list of plays made so far.
%
echo([]) ->
paper;
echo([Last|_]) ->
% play whatever the opponent played last.
Last.
rock(_) ->
% play always 'rock', no matter previous opponents moves
rock.
no_repeat([]) ->
% play something that does not repeat the opponent's last play:
paper;
no_repeat([X|_Xs]) ->
case X of
rock -> scissors;
paper -> rock;
scissors -> paper
end.
const(_Xs) ->
% play always the same choice
paper.
cycle(Xs) ->
% cycles through the three choices in sequence
enum(length(Xs) rem 3).
rand(_) ->
% choice made at random
enum(rand:uniform(3)-1).
least_freq_played(Xs) ->
{R,P,S} = type_count(Xs),
{_, T} = min(R, min(P, S)),
T.
rand_strategy(Xs)->
Choice = rand:uniform(8),
%io:format("strategy ~p ~n",[Choice]),
case Choice of
1->least_freq_played(Xs);
2->most_freq_played(Xs);
3->rand(Xs);
4->rock(Xs);
5->cycle(Xs);
6->no_repeat(Xs);
7->echo(Xs);
8->rock(Xs)
end.
% most_freq
most_freq_played(Xs) ->
{R,P,S} = type_count(Xs),
{_, T} = max(R, max(P, S)),
T.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Strategy vs Strategy
% Define a function that takes three arguments: two strategies and a number N, and which plays
% the strategies against each other for N turns. At each stage the function should output the result of the
% round, and it should show the result of the tournament at the end.
%
% play one strategy against another, for N moves.
% example:
% rps:play_two(fun rps:rand/1,fun rps:rand/1,30).
% rps:play_two(fun rps:least_freq_played/1,fun rps:no_repeat/1,30).
% rps:play_two(fun rps:least_freq_played/1,fun rps:most_freq_played/1,30).
% rps:play_two(fun rps:rand_strategy/1,fun rps:rand_strategy/1,30).
play_two(StrategyL,StrategyR,N) ->
% given a strategy function for each player, play them out for N moves
play_two(StrategyL,StrategyR,[],[],N).
% tail recursive loop for play_two/3
% 0 case computes the result of the tournament
play_two(_,_,PlaysL,PlaysR,0) ->
% if we played out all the moves, report the final result
io:format("Final score for left player: ~p~n", [tournament(PlaysL, PlaysR)]);
play_two(StrategyL,StrategyR,PlaysL,PlaysR,N) ->
PlayL = StrategyL(PlaysR),
PlayR = StrategyR(PlaysL),
Result = result(PlayL, PlayR),
case Result of
draw -> io:format("turn ~p ~p vs ~p, ~p.~n", [N,PlayL, PlayR, Result]);
_ -> io:format("turn ~p ~p vs ~p, ~p for left player.~n", [N,PlayL, PlayR, Result])
end,
case abs(tournament(PlaysL, PlaysR)) > 10 of % if winning or losing by a lot, end the game
true -> MovesLeft = 1 ;
false-> MovesLeft = N
end,
play_two(StrategyL, StrategyR, [PlayL | PlaysL], [PlayR | PlaysR], MovesLeft - 1).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% tests
result_test() ->
?assertEqual(result(rock,rock), draw),
?assertEqual(result(rock,paper), lose),
?assertEqual(result(rock,scissors), win),
?assertEqual(result(paper,rock), win),
?assertEqual(result(paper,paper), draw),
?assertEqual(result(paper,scissors), lose),
?assertEqual(result(scissors,rock), lose),
?assertEqual(result(scissors,paper), win),
?assertEqual(result(scissors,scissors), draw).
tournament_test() ->
?assertEqual(tournament([rock],[rock]), 0),
?assertEqual(tournament([rock,rock],[rock,paper]), -1),
?assertEqual(tournament([rock,rock,paper],[rock,paper,scissors]), -2),
?assertEqual(tournament([rock,rock,paper,paper],[rock,paper,scissors,rock]), -1).
erlang rps.erl
rps.erl
-module(rps).
-export([result/2, tournament/2, test/0]).
-include_lib("eunit/include/eunit.hrl").
beat(rock) -> paper;
beat(paper) -> scissors;
beat(scissors) -> rock.
%% lose(rock) -> scissors;
%% lose(paper) -> rock;
%% lose(scissors) -> paper.
result(X, X) -> 0;
result(X, Y) ->
case beat(Y) of
X -> 1;
_ -> -1
end.
tournament(L, R) ->
lists:foldr(fun({X, Y}, Acc) -> result(X, Y) + Acc end, 0, lists:zip(L, R)).
test() ->
?assert(tournament([rock,rock,paper,paper],[rock,paper,scissors,rock]) =:= -1),
ok.
erlang index.erl
index.erl
-module(index).
-export([index_file/1,show_index/1,get_file_contents/1,show_file_contents/1]).
-include_lib("eunit/include/eunit.hrl").
%%% ASSIGNMENT 2.20 - INDEXING WORDS IN LINES FROM A TEXT FILE
% Example files available in:
% gettysburg-address.txt (short)
% dickens-christmas.txt (long)
% THIS VERSION: "ATTACK OF THE LIST-CLONES!"
% NEXT VERSION: "LIST COMPREHENSIONS (AND FRIENDS) FIGHT BACK!"
%
% You are floating on the sun drench undulating waves of a vast ocean when
% you catch a golden glint from the depths. It's deep, very deep, but you
% are enticed by the thought of riches-beyond-reason and down you dive...
% STRATEGY
%
% Index creation:
% - For each new word
% - Find insertion point in Index (before/word-idx/after)
% - Insert {Word,[LineNum]} into Index
% - Each index to use linear list of line numbers,
% - When source read, transform each list into list of {From,To} ranges.
%
% Stop Words:
% - simple list to start with,
% - candidates extracted from corpus based on length and/or freq,
% - file-based, possibly based on std list generally used.
%
% Stemming:
% - boolean switch for extended search
% - simple version using word + common endings, eg, "s","er","ing", etc
%
% IMPROVEMENTS:
% - tests
% - more comments
% - dict/map for word-based look-up
% - list comprehensions (and anything else to shorten this program!)
% - boolean search terms
% - better output formatting
% - include count of repeats per word per line for, eg, word-freq. Or...
% - preserve file name for, eg, back-referencing, word-freq.
% "CONSTANTS"
% Stop Words
% Since words with 3 or less letters are excluded, only stop words with four or
% more letters are needed. TBD: candidates, file-based.
stop_words() -> ["have","come","that","those","here"]. % sample
% Stemming (TBD)
% stemmings() -> ["s","er","ing"]. % sample
% INDEXING
% Build Index: simply collect indexes, even repeats, eg,
% { "foo" , [3,4,4,5,7,11,12,13] }
index_file(FileName) ->
Lines = get_file_contents(FileName),
condense_index([],index_lines(Lines)).
condense_index(NewIndex,[]) -> lists:reverse(NewIndex);
condense_index(NewIndex,[{Word,Lines}|Xs]) ->
condense_index([{Word,condense_lines(Lines)}|NewIndex],Xs).
condense_lines([N]) -> [{N,N}];
condense_lines([X|Xs]) -> to_ranges([],{X,X},Xs).
to_ranges(R,{H,L},[]) -> [{L,H}|R];
to_ranges(R,{H,L},[H|Xs]) -> to_ranges(R,{H,L},Xs);
to_ranges(R,{H,L},[N|Xs]) ->
case N==H-1 of
true -> to_ranges(R,{N,L},Xs);
_ -> to_ranges([{L,H}|R],{N,N},Xs)
end.
% INDEXING - EXTRAS (TBD)
show_index([]) -> ok;
show_index([{Word,Lines}|Xs]) ->
io:format("~s: ~w ~n",[Word,Lines]),
show_index(Xs).
candidate_stop_words(MinLength,Index) ->
candidate_stop_words(MinLength,Index,[]).
candidate_stop_words(_MinLength,[],Stops) -> Stops;
candidate_stop_words(MinLength,[L={Word,Lines}|Xs],Stops) ->
case length(Word)>=MinLength of
true -> candidate_stop_words(MinLength,Xs,[L|Stops]);
_ -> candidate_stop_words(MinLength,Xs,Stops)
end.
% search(Word,Stemming,Index) -> [].
% line_count(Word,Index) -> 0.
% You encounter a school of deadly jellyfish and slowly navigate your ways
% around them...
% INDEXING - HELPERS
% Lines
index_lines(Lines) -> index_lines(1,Lines,[]).
index_lines(_LineNum,[],Index) -> Index;
index_lines(LineNum,[[]|Lines],Index) -> index_lines(LineNum+1,Lines,Index);
index_lines(LineNum,[Line|Lines],Index) ->
index_lines(LineNum+1,Lines,index_words(LineNum,words(Line),Index)).
index_words(_LineNum,[],Index) -> Index;
index_words(LineNum,[Word|Words],Index) ->
index_words(LineNum,Words,upsert(LineNum,Word,Index)).
upsert(LineNum,Word,Index) ->
join_no_reverse(upsert(LineNum,Word,[],Index)).
upsert(LineNum,Word,L0,[]) -> [L0,[{Word, [LineNum]}]];
upsert(LineNum,Word,L0,L1=[{AWord,_Lines}|_Xs]) when AWord>Word ->
[L0,[{Word, [LineNum]}|L1]];
upsert(LineNum,Word,L0,[{Word,Lines}|Xs]) ->
[L0,[{Word,[LineNum|Lines]}|Xs]]; % PRESERVING REPEATS FOR NOW!
upsert(LineNum,Word,L0,[X|Xs]) -> upsert(LineNum,Word,[X|L0],Xs).
% JOIN (one list to another)
join_no_reverse([L0,L1]) -> join_no_reverse(L0,L1).
join_no_reverse([],L1) -> L1;
join_no_reverse([X|Xs],L1) -> join_no_reverse(Xs,[X|L1]).
% Words
words(Line) ->
% valid_words(string:tokens(only_alphas(Line)," "),[]).
S = only_alphas(Line),
% io:format("~p~n",[S]),
Words = string:tokens(S," "),
% io:format("~p~n",[Words]),
valid_words(Words,[]).
valid_words([],Words) -> Words;
valid_words([Word|Xs],Words) ->
case is_valid_word(Word) of
true -> valid_words(Xs,[Word|Words]);
_ -> valid_words(Xs,Words)
end.
is_valid_word(Word) -> not_short_word(Word) andalso not_stop_word(Word).
not_short_word(Word) -> length(Word)>3.
not_stop_word(Word) -> not lists:member(Word,stop_words()).
% Below the jellyfish, you suddenly realise you have took too long and gone
% too deep. Your lungs begin to strain, you are doomed. The golden glint is
% still there so, resigned to your fate, you decide to carry on to see what
% you risked everything for...
% Alpha's & Spaces Filter: exclude all other chars (and extra spaces)
only_alphas(S) -> only_chars(lcase(S),[32|a2z()],[]).
only_chars([],_Chars,Acc) -> lists:reverse(Acc);
% only_Chars([],_Chars,Acc) -> Acc;
only_chars([" "|[" "|Xs]],Chars,Acc) -> only_chars([" "|Xs],Chars,Acc);
only_chars([X|Xs],Chars,Acc) ->
case lists:member(X,Chars) of
true -> only_chars(Xs,Chars,[X|Acc]);
_ -> only_chars(Xs,Chars,Acc)
end.
lcase(S) -> string:to_lower(S). % Cheat!
a2z() -> lists:seq($a,$z).
% Stems
stemmed(Word) -> stemmed(stemmings,[Word],Word).
stemmed([],Stems,_Word) -> Stems;
stemmed([X|Xs],Stems,Word) -> stemmed(Xs,[Word++X|Stems],Word).
% FILE HANDLING
% Get the contents of a text file into a list of lines.
% Each line has its trailing newline removed.
get_file_contents(Name) ->
{ok,File} = file:open(Name,[read]),
Rev = get_all_lines(File,[]),
lists:reverse(Rev).
% Auxiliary function for get_file_contents (not exported).
get_all_lines(File,Partial) ->
case io:get_line(File,"") of
eof -> file:close(File),
Partial;
Line -> {Strip,_} = lists:split(length(Line)-1,Line),
get_all_lines(File,[Strip|Partial])
end.
% Show the contents of a list of strings.
% Can be used to check the results of calling get_file_contents.
show_file_contents([]) ->
ok;
show_file_contents([L|Ls]) ->
io:format("~s~n",[L]),
show_file_contents(Ls).
% TESTS (TBD)
% Reaching the bottom you discover the golden glint is just the sun reflecting
% off an abandoned scuba-diving mask. Luckily it's attached to an air tank with
% just enough air to get you back to the surface. Phew!
erlang index.erl
index.erl
-module(index).
-export([index_gettysburg/0,index_dickens/0]).
% Get the contents of a text file into a list of lines.
get_file_contents(Name) ->
{ok,File} = file:open(Name,[read]),
Rev = get_all_lines(File,[]),
lists:reverse(Rev).
% Auxiliary function for get_file_contents.
get_all_lines(File,Partial) ->
case io:get_line(File,"") of
eof -> file:close(File),
Partial;
Line -> {Strip,_} = lists:split(length(Line)-1,Line),
get_all_lines(File,[Strip|Partial])
end.
% Show the contents of a list of strings.
show_file_contents([L|Ls]) ->
io:format("~s~n",[L]),
show_file_contents(Ls);
show_file_contents([]) ->
ok.
% Convert list of tuples to a string
list_of_tuples_to_string([]) ->
"";
list_of_tuples_to_string([L|Ls]) ->
io_lib:format("~p",[L]) ++ list_of_tuples_to_string(Ls).
% Index the gettysburg address
index_gettysburg() ->
index_file('gettysburg-address.txt').
% Index dicken's christmas carol
index_dickens() ->
index_file('dickens-christmas.txt').
% Index and display a file
index_file(FileName) ->
show_file_contents(list_of_tuples_to_string(index(FileName))).
% Index a text file by line number
index(FileName) ->
Lines=get_file_contents(FileName),
Lines_numbered=number_lines(Lines),
Words=words_from_list(Lines),
Words_and_linenumbers=words_and_linenumbers(Words, Lines_numbered),
{Words,LineNumbers}=lists:unzip(Words_and_linenumbers),
lists:zip(Words,list_of_numbers_to_ranges(LineNumbers)).
% Transform list of linenumbers lists to list of linenumber ranges
list_of_numbers_to_ranges([]) -> [];
list_of_numbers_to_ranges([X|Xs]) ->
[numbers_to_ranges(X)|list_of_numbers_to_ranges(Xs)].
% Transform linenumber list to range
numbers_to_ranges(Xs) ->
numbers_to_ranges(Xs,[]).
numbers_to_ranges([],Acc) ->
lists:reverse(Acc);
numbers_to_ranges([X|Xs],[]) ->
numbers_to_ranges(Xs,[{X,X}]);
numbers_to_ranges([X|Xs],[{Low,High}|Ys]) when X-High==1 ->
numbers_to_ranges(Xs,[{Low,X}|Ys]);
numbers_to_ranges([X|Xs],Acc) ->
numbers_to_ranges(Xs,[{X,X}|Acc]).
% Number a list of strings into a list of tuples
number_lines(Lines) -> number_lines(Lines, 1).
number_lines([], _N) -> [];
number_lines([X|Xs], N) ->
case X == [] of
true -> number_lines(Xs, N+1);
false -> [{N,X} | number_lines(Xs, N+1)]
end.
% Split a list of lines of text into a list of words
words_from_lines(Lines) ->
words_from_lines(Lines,[]).
words_from_lines([], Acc) ->
lists:concat(Acc);
words_from_lines([Line|Lines], Acc) ->
words_from_lines(Lines, [words_from_line(Line) | Acc]).
% Split a line on words and punctuation
words_from_line(Line) ->
string:tokens(Line, " ;!:?-.,\\\t()\"").
% Remove words less that length 3
remove_short_words(Xs) ->
lists:filter(fun(X)->length(X)>3 end, Xs).
% Remove duplicates
remove_duplicates([]) -> [];
remove_duplicates([X|Xs]) ->
[X|remove_duplicates(lists:filter(fun(Y)->X=/=Y end,Xs))].
% Return sorted and pruned list of Words from list of strings
words_from_list(Lines) ->
Words1=words_from_lines(Lines),
Words2=lists:sort(Words1),
Words3=remove_short_words(Words2),
remove_duplicates(Words3).
% Transform a list of Words and Lines_numbered to list of tuples
words_and_linenumbers([],_Lines_numbered) ->
[];
words_and_linenumbers([Word|Words],Lines_numbered) ->
LineNumbers=line_numbers_has_word(Word, Lines_numbered),
[{Word,LineNumbers} | words_and_linenumbers(Words,Lines_numbered)].
% Return list of line numbers of Lines_numbered that contain Word
line_numbers_has_word(Word, Lines_numbered) ->
{LineNumbers,_}=lists:unzip(lists:filter(fun({_Number,Line}) -> lists:member(Word,words_from_line(Line)) end, Lines_numbered)),
LineNumbers.
erlang Erlang中的函数式编程 - 索引程序。
Erlang中的函数式编程 - 索引程序。
index.erl
-module(index).
-export([get_file_contents/1,show_file_contents/1]).
-export([index_file/1,print_index/1]).
-export([partition_test/0]).
%% The main prorgram.
%% Run:
%% index:index_file("gettysburg-address.txt").
%% to produce a list of word indexes.
%%
%% To produce a "formated" index listing
%% Run:
%% index:print_index(index:index_file("gettysburg-address.txt")).
%%
index_file(File) ->
Lines = get_file_contents(File),
Words = process_lines(Lines),
collect_words(Words).
%% Count and process each line of text, produces a list of {Word, Line_Number} pairs.
process_lines(Lines) -> process_lines(Lines, 0, []).
process_lines([], _Count, Acc) -> lists:sort(Acc);
process_lines([L|Ls], Count, Acc) ->
Next = Count+ 1,
process_lines(Ls, Next, index_line(L, Next, Acc)).
%% Given a line of text, split line into words and produce a list of Word, Line_number pairs.
index_line(Line, Count, Acc) ->
Toks = string:tokens(Line, " \t\n\b\r\\.,:;!?=-"),
Words = clean_words(Toks),
index_words(Words, Count, Acc).
%% Given a list of words produces a list of lowercase words above a certain length.
clean_words([]) -> [];
clean_words([X|Xs]) ->
case string:len(X) > 3 of
true -> [string:to_lower(X) | clean_words(Xs)];
false -> clean_words(Xs)
end.
%% Given a list of Words and a Line_number, produce a list of Word, Line_Number pairs.
index_words([], _, Acc) -> Acc;
index_words([T|Ts], Count, Acc) ->
index_words(Ts, Count, [{T, Count} | Acc]).
%% Given a sorted list of {Word, Line_Numbers} pairs collect into a list of {Word, List-0f-Line-Numbers}.
collect_words(Wrds) -> lists:sort(collect_words(Wrds, [], [])).
collect_words([{Wrd,Ln}], Acc, Idx) ->
[{Wrd,lists:sort([Ln|Acc])}|Idx];
collect_words([{Wrd,Ln1},{Wrd,_Ln2}|Wrds], Acc, Idx) ->
collect_words(Wrds,[Ln1|Acc], Idx);
collect_words([{Wrd1,Ln1},{_Wrd2,_Ln2}|Wrds], Acc, Idx) ->
Index = partition([Ln1|Acc]),
collect_words(Wrds, [], [{Wrd1, Index}|Idx]).
%% partition a list of numbers into a list of Number Range Pairs.
%% See test function for examples.
partition(Lst) -> partition(lists:sort(Lst), []).
partition([], Acc) -> lists:reverse(Acc);
partition([X|Xs], [{Start}|Acc]) when X == Start+1 ->
partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], [{Start,X}|Acc]) ->
partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], [{Start,End}|Acc]) when X == End+1 ->
partition(Xs, [{Start,X}|Acc]);
partition([X|Xs], Acc) ->
partition(Xs, [{X,X}|Acc]).
partition_test() ->
[{1,1}] = partition([1]),
[{1,1}] = partition([1,1,1]),
[{1,2}] = partition([2,1,1]),
[{1,3}] = partition([3,2,2,1,1]),
[{1,3},{5,5}] = partition([5,5,3,2,2,1]),
[{1,1},{3,3},{5,7}] = partition([7,7,6,5,3,1,1]),
ok.
%% Print out file world index
print_index([]) -> ok;
print_index([X|Xs]) ->
print_item(X),
print_index(Xs).
% print single word index item
print_item({Word, Ranges}) ->
io:format("~p ", [Word]),
print_ranges(Ranges),
io:format("~n").
% print the list of ranges
print_ranges([]) -> done;
print_ranges([{Beg,End}|Xs]) ->
io:format(",{~p,~p}", [Beg,End]),
print_ranges(Xs);
print_ranges([X]) -> % sidestep a partition function bug.
io:format("{~p,~p}", [X,X]),
print_ranges([]).
% Used to read a file into a list of lines.
% Example files available in:
% gettysburg-address.txt (short)
% dickens-christmas.txt (long)
% Get the contents of a text file into a list of lines.
% Each line has its trailing newline removed.
get_file_contents(Name) ->
{ok,File} = file:open(Name,[read]),
Rev = get_all_lines(File,[]),
lists:reverse(Rev).
% Auxiliary function for get_file_contents.
% Not exported.
get_all_lines(File,Partial) ->
case io:get_line(File,"") of
eof -> file:close(File),
Partial;
Line -> {Strip,_} = lists:split(length(Line)-1,Line),
get_all_lines(File,[Strip|Partial])
end.
% Show the contents of a list of strings.
% Can be used to check the results of calling get_file_contents.
show_file_contents([L|Ls]) ->
io:format("~s~n",[L]),
show_file_contents(Ls);
show_file_contents([]) ->
ok.