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.