├── .github └── workflows │ └── erlang.yml ├── .gitignore ├── LICENSE ├── README.md ├── ebin └── mod_rabbitmq.beam ├── include ├── Data.hrl ├── Debug.hrl └── Shapes.hrl ├── lib ├── barber │ ├── README.md │ ├── rebar.config │ ├── rebar.lock │ └── src │ │ ├── barber.app.src │ │ ├── barber.erl │ │ └── customer.erl ├── examples │ ├── include │ │ └── Trees.hrl │ ├── rebar.config │ ├── src │ │ ├── examples.app.src │ │ ├── poker.erl │ │ ├── prog_erl.erl │ │ ├── recursion.erl │ │ ├── ring.erl │ │ ├── ring2.erl │ │ └── trees.erl │ └── test │ │ ├── recursion_tests.erl │ │ └── trees_test.erl ├── mastermind │ ├── README.md │ ├── rebar.config │ ├── rebar.lock │ └── src │ │ ├── codemaker.erl │ │ └── mastermind.app.src └── ndpar │ ├── doc │ └── overview.edoc │ ├── rebar.config │ ├── rebar.lock │ ├── src │ ├── bin.erl │ ├── celebrities.erl │ ├── century.erl │ ├── core.erl │ ├── galois.erl │ ├── lazy.erl │ ├── lazy2.erl │ ├── marking.erl │ ├── maths.erl │ ├── mintrees.erl │ ├── modules.erl │ ├── ndpar_lib.app.src │ ├── pkix.erl │ ├── primes.erl │ ├── ravel.erl │ ├── rnd.erl │ ├── rsa.erl │ ├── rsa_private_key.erl │ └── saddleback.erl │ └── test │ ├── rsa_private_key_tests.erl │ └── saddleback_tests.erl ├── mycache ├── README ├── ebin │ ├── mycache.app │ ├── start-master.sh │ ├── start-slave1.sh │ └── start-slave2.sh ├── java │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── ndpar │ │ │ └── erlcache │ │ │ └── ErlStringMap.java │ │ └── test │ │ └── java │ │ └── com │ │ └── ndpar │ │ └── erlcache │ │ └── ErlStringMapTest.java └── src │ ├── mycache.erl │ ├── mycache.hrl │ ├── mycache_app.erl │ ├── mycache_boot.erl │ └── mycache_sup.erl ├── mydb ├── bin │ ├── mydb.script │ └── start.sh ├── ebin │ ├── mydb.app │ └── mydb.rel └── src │ ├── db.erl │ ├── mydb.erl │ ├── mydb_app.erl │ └── mydb_sup.erl ├── rebar.config ├── rebar.lock ├── src ├── add_two.erl ├── bool.erl ├── db2.erl ├── db3.erl ├── db3_server.erl ├── db3_server_test.erl ├── echo.erl ├── event_manager.erl ├── factorial.erl ├── frequency.erl ├── frequency2.erl ├── frequency_reliable.erl ├── generic_server.erl ├── hof.erl ├── id_generator.erl ├── io_handler.erl ├── life.erl ├── life_async.erl ├── life_async_grid.erl ├── list_server.erl ├── log_handler.erl ├── mutex.erl ├── mutex_monitor.erl ├── mutex_monitor_test.erl ├── mutex_reliable.erl ├── mutex_reliable_test.erl ├── my_db.erl ├── my_lib.erl ├── my_lists.erl ├── my_supervisor.erl ├── my_supervisor2.erl ├── my_supervisor2_test.erl ├── myring.erl ├── phone.erl ├── poisson.erl ├── records1.erl ├── records1_test.erl ├── shapes.erl ├── shapes_test.erl ├── show_eval_test.erl ├── stats_handler.erl ├── turnstile.erl ├── turnstile2.erl └── turnstile_gen.erl ├── tcp └── src │ ├── echo_active.erl │ ├── echo_passive.erl │ ├── http_proxy.erl │ ├── http_snoop.erl │ └── peer.erl └── test ├── ets_tests.erl └── regex_tests.erl /.github/workflows/erlang.yml: -------------------------------------------------------------------------------- 1 | name: Erlang CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | container: 16 | image: erlang:22.0.7 17 | 18 | steps: 19 | - uses: actions/checkout@v2 20 | - name: Compile 21 | run: rebar3 compile 22 | - name: Run tests 23 | run: rebar3 do eunit, ct 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .deps_plt 2 | .eunit 3 | *.beam 4 | _build 5 | /ebin 6 | /temp 7 | 8 | **/doc/* 9 | !**/doc/overview.edoc 10 | 11 | /mycache/ebin/Mnesia* 12 | /mycache/ebin/*.beam 13 | /mycache/java/.classpath 14 | /mycache/java/.project 15 | /mycache/java/target 16 | 17 | /mydb/bin/*.boot 18 | 19 | /tcp/ebin 20 | 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ndpar_lib 2 | ===== 3 | 4 | This repository is a collection of algorithms and exercises from the following books 5 | 6 | - [Programming Erlang](https://pragprog.com/titles/jaerlang2/programming-erlang-2nd-edition/) (2nd edition) by Joe Armstrong [A2] 7 | - [Pearls of Functional Algorithm Design](https://www.cambridge.org/core/books/pearls-of-functional-algorithm-design/B0CF0AC5A205AF9491298684113B088F) by Richard Bird [B1] 8 | - [Introduction to Algorithms](https://mitpress.mit.edu/books/introduction-algorithms-third-edition) (3rd edition) by Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, and Clifford Stein [CLRS3] 9 | - [Erlang Programming](http://shop.oreilly.com/product/9780596518189.do) by Francesco Cesarini and Simon Thompson [CT1] 10 | - [Cryptography Engineering](https://www.schneier.com/books/cryptography_engineering/) by Niels Ferguson, Bruce Schneier, and Tadayoshi Kohno [FSK1] 11 | - [Erlang and OTP in Action](https://www.manning.com/books/erlang-and-otp-in-action) by Martin Logan, Eric Merritt, and Richard Carlsson [LMC1] 12 | - [Handbook of Applied Cryptography](http://cacr.uwaterloo.ca/hac/) by Alfred J. Menezes, Paul C. van Oorschot, Scott A. Vanstone [MvOV1] 13 | 14 | Tested with Erlang/OTP 23. 15 | 16 | Build 17 | ----- 18 | 19 | $ rebar3 compile 20 | $ rebar3 dialyzer 21 | 22 | Test 23 | ----- 24 | 25 | $ rebar3 eunit 26 | 27 | Documentation 28 | ----- 29 | 30 | $ rebar3 edoc 31 | $ open lib/ndpar/doc/index.html 32 | 33 | REPL 34 | ----- 35 | 36 | $ rebar3 shell -------------------------------------------------------------------------------- /ebin/mod_rabbitmq.beam: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ndpar/erlang/e215841a1d370e0fc5eb6b9ff40ea7ae78fc8763/ebin/mod_rabbitmq.beam -------------------------------------------------------------------------------- /include/Data.hrl: -------------------------------------------------------------------------------- 1 | -record(data, {key, value}). -------------------------------------------------------------------------------- /include/Debug.hrl: -------------------------------------------------------------------------------- 1 | -ifdef(show). 2 | -define(SHOW_EVAL(Expression), Exp = Expression, io:format("SHOW: ~p~n", [Exp]), Exp). 3 | -else. 4 | -define(SHOW_EVAL(Expression), Expression). 5 | -endif. 6 | -------------------------------------------------------------------------------- /include/Shapes.hrl: -------------------------------------------------------------------------------- 1 | -record(circle, {radius}). 2 | -record(rectangle, {length, width}). 3 | -record(triangle, {a, b, c}). -------------------------------------------------------------------------------- /lib/barber/README.md: -------------------------------------------------------------------------------- 1 | # Generic State Machine 2 | 3 | This example demonstrates interaction between two state machines. 4 | It also shows how to use the following features of `gen_statem` 5 | 6 | - sending events to itself 7 | - state timeouts 8 | - postponed events 9 | - reply actions 10 | 11 | To run the demo, execute these commands in Erlang shell: 12 | 13 | 1> barber:open_shop(). 14 | 2> barber:new_customer(customer:start()). 15 | 3> barber:new_customer(customer:start()). 16 | 4> barber:new_customer(customer:start()). 17 | 5> barber:new_customer(customer:start()). 18 | 6> barber:close_shop(). 19 | 20 | For the background information and the previous implementation, see this 21 | [blog post](https://blog.ndpar.com/2013/06/11/sleeping-barber-in-erlang/). -------------------------------------------------------------------------------- /lib/barber/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /lib/barber/rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /lib/barber/src/barber.app.src: -------------------------------------------------------------------------------- 1 | {application, barber, 2 | [{description, "State Machine Demo"}, 3 | {vsn, "0.2.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib 8 | ]}, 9 | {env, []}, 10 | {modules, []}, 11 | 12 | {maintainers, ["Andrey Paramonov"]}, 13 | {licenses, ["Apache 2.0"]}, 14 | {links, [{"GitHub", "https://github.com/ndpar/erlang/tree/master/lib/barber"}]} 15 | ]}. 16 | -------------------------------------------------------------------------------- /lib/barber/src/barber.erl: -------------------------------------------------------------------------------- 1 | -module(barber). 2 | -author("Andrey Paramonov "). 3 | -behaviour(gen_statem). 4 | 5 | %% API 6 | -export([open_shop/0, new_customer/1, close_shop/0]). 7 | 8 | %% Callback functions 9 | -export([init/1, callback_mode/0, terminate/3, code_change/4]). 10 | 11 | %% FSM states 12 | -export([sleep/3, ready/3, busy/3]). 13 | 14 | -define(SERVER, ?MODULE). 15 | -define(HAIRCUT_TIME, 5000). 16 | -define(ROOM_SIZE, 3). 17 | 18 | -record(state, {room = 0}). 19 | 20 | %%==================================================================== 21 | %% API 22 | %%==================================================================== 23 | 24 | open_shop() -> 25 | gen_statem:start_link({local, ?SERVER}, ?MODULE, [], []). 26 | 27 | close_shop() -> 28 | gen_statem:stop(?SERVER). 29 | 30 | -spec new_customer(pid()) -> ok. 31 | new_customer(Customer) -> 32 | gen_statem:cast(?SERVER, {new, Customer}). 33 | 34 | %%==================================================================== 35 | %% gen_statem callbacks 36 | %%==================================================================== 37 | 38 | callback_mode() -> 39 | state_functions. 40 | 41 | init([]) -> 42 | log("Shop is open. zzzzZ"), 43 | {ok, sleep, #state{}}. 44 | 45 | terminate(_Reason, _StateName, _StateData) -> 46 | log("Shop is closed. Bye."), 47 | ok. 48 | 49 | code_change(_OldVsn, StateName, StateData, _Extra) -> 50 | {ok, StateName, StateData}. 51 | 52 | %%==================================================================== 53 | %% FSM states and transitions 54 | %%==================================================================== 55 | 56 | sleep(cast, {new, Customer}, #state{room = 0} = StateData) -> 57 | log("Waking up for ~p.", [Customer]), 58 | {next_state, ready, StateData, {next_event, cast, {new, Customer}}}. 59 | 60 | ready(cast, {new, Customer}, StateData) -> 61 | log("Good morning ~p. Please take a seat.", [Customer]), 62 | customer:sit_down(Customer), 63 | {next_state, busy, StateData, {state_timeout, ?HAIRCUT_TIME, Customer}}. 64 | 65 | busy(state_timeout, Customer, #state{room = Room}) -> 66 | log("Do you like your haircut ~p?", [Customer]), 67 | ok = customer:done(Customer), 68 | case Room of 69 | 0 -> 70 | log("Time for nap. zzzzZ"), 71 | {next_state, sleep, #state{}}; 72 | _ -> 73 | log("Next please.", []), 74 | {next_state, ready, #state{}} % postponed events will re-increment room on re-play 75 | end; 76 | 77 | busy(cast, {new, Customer}, #state{room = ?ROOM_SIZE} = StateData) -> 78 | log("Sorry ~p. No room left.", [Customer]), 79 | customer:sorry(Customer), 80 | {next_state, busy, StateData}; 81 | 82 | busy(cast, {new, _Customer}, #state{room = Room}) -> 83 | {next_state, busy, #state{room = Room + 1}, postpone}. 84 | 85 | %%==================================================================== 86 | %% Helper functions 87 | %%==================================================================== 88 | 89 | log(Message) -> 90 | log(Message, []). 91 | 92 | log(Message, Args) -> 93 | error_logger:info_msg("~p: " ++ Message ++ "~n", [self() | Args]). 94 | -------------------------------------------------------------------------------- /lib/barber/src/customer.erl: -------------------------------------------------------------------------------- 1 | -module(customer). 2 | -author("Andrey Paramonov "). 3 | -behaviour(gen_statem). 4 | 5 | %% API 6 | -export([start/0, sit_down/1, done/1, sorry/1]). 7 | 8 | %% Callback functions 9 | -export([init/1, callback_mode/0, terminate/3, code_change/4]). 10 | 11 | %% FSM states 12 | -export([waiting/3, served/3]). 13 | 14 | %%==================================================================== 15 | %% API 16 | %%==================================================================== 17 | 18 | start() -> 19 | {ok, Pid} = gen_statem:start(?MODULE, [], []), 20 | Pid. 21 | 22 | sit_down(Customer) -> 23 | gen_statem:cast(Customer, sit_down). 24 | 25 | done(Customer) -> 26 | gen_statem:call(Customer, exit). 27 | 28 | sorry(Customer) -> 29 | gen_statem:cast(Customer, exit). 30 | 31 | %%==================================================================== 32 | %% gen_statem callbacks 33 | %%==================================================================== 34 | 35 | callback_mode() -> 36 | state_functions. 37 | 38 | init([]) -> 39 | log("Good morning."), 40 | {ok, waiting, unow()}. 41 | 42 | terminate(_Reason, _StateName, _StateData) -> 43 | ok. 44 | 45 | code_change(_OldVsn, StateName, StateData, _Extra) -> 46 | {ok, StateName, StateData}. 47 | 48 | %%==================================================================== 49 | %% FSM states and transitions 50 | %%==================================================================== 51 | 52 | waiting(cast, sit_down, WaitStart) -> 53 | log("I've been waiting for ~p sec.", [duration(WaitStart)]), 54 | {next_state, served, unow()}; 55 | 56 | waiting(cast, exit, _StateData) -> 57 | log("Maybe tomorrow."), 58 | {stop, normal, undefined}. 59 | 60 | served({call, Barber}, exit, ServiceStart) -> 61 | log("Thank you. The haircut took ~p sec.", [duration(ServiceStart)]), 62 | {stop_and_reply, normal, {reply, Barber, ok}}. 63 | 64 | %%==================================================================== 65 | %% Helper functions 66 | %%==================================================================== 67 | 68 | log(Message) -> 69 | log(Message, []). 70 | 71 | log(Message, Args) -> 72 | error_logger:info_msg("~p: " ++ Message ++ "~n", [self() | Args]). 73 | 74 | unow() -> 75 | calendar:universal_time(). 76 | 77 | duration(Start) -> 78 | {_, {_, _, Seconds}} = calendar:time_difference(Start, unow()), 79 | Seconds. 80 | -------------------------------------------------------------------------------- /lib/examples/include/Trees.hrl: -------------------------------------------------------------------------------- 1 | -record(bintree, {value, left, right}). 2 | -------------------------------------------------------------------------------- /lib/examples/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /lib/examples/src/examples.app.src: -------------------------------------------------------------------------------- 1 | {application, examples, 2 | [{description, "Erlang examples and demos"}, 3 | {vsn, "0.1.0"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | {maintainers, ["Andrey Paramonov"]}, 8 | {licenses, ["Apache 2.0"]}, 9 | {links, [{"GitHub", "https://github.com/ndpar/erlang/tree/master/lib/examples"}]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /lib/examples/src/prog_erl.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Exercises from the book [A2]. 3 | %% 4 | -module(prog_erl). 5 | -author("Andrey Paramonov"). 6 | 7 | -export([die_after/1, my_spawn/3]). 8 | -export([my_spawn2/3]). 9 | -export([my_spawn/4]). 10 | -export([ch_13_4/0, keep_alive/2]). 11 | -export([ch_13_5/1, keep_alive/1]). 12 | -export([ch_13_6/0]). 13 | 14 | %% ============================================================================= 15 | %% Chapter 13. Errors in Concurrent Programs 16 | %% ============================================================================= 17 | 18 | %% 19 | %% Exercise 1. 20 | %% Write a function my_spawn(Mod, Func, Args) that behaves like spawn(Mod, Func, 21 | %% Args) but with one difference. If the spawned process dies, a message should 22 | %% be printed saying why the process died and how long the process lived for 23 | %% before it died. 24 | %% 25 | %% > prog_erl:my_spawn(prog_erl, die_after, [5000]). 26 | %% 27 | -spec my_spawn(Module :: module(), Function :: atom(), Args :: [term()]) -> pid(). 28 | 29 | my_spawn(Mod, Func, Args) -> 30 | spawn(fun() -> 31 | Pid = spawn(Mod, Func, Args), 32 | Born = erlang:system_time(millisecond), 33 | Ref = monitor(process, Pid), 34 | receive 35 | {'DOWN', Ref, process, Pid, Why} -> 36 | Died = erlang:system_time(millisecond), 37 | io:format("~p lived for ~p ms, died with: ~p~n", [Pid, Died - Born, Why]) 38 | end 39 | end). 40 | 41 | die_after(Millis) -> 42 | io:format("~p is going to die in ~p ms~n", [self(), Millis]), 43 | receive 44 | after Millis -> 1 / 0 45 | end. 46 | 47 | %% 48 | %% Exercise 2. 49 | %% Solve the previous exercise using the on_exit function shown earlier in this 50 | %% chapter. 51 | %% 52 | %% > prog_erl:my_spawn2(prog_erl, die_after, [5000]). 53 | %% 54 | 55 | on_exit(Pid, Fun) -> 56 | spawn(fun() -> 57 | Ref = monitor(process, Pid), 58 | receive 59 | {'DOWN', Ref, process, Pid, Why} -> Fun(Why) 60 | end 61 | end). 62 | 63 | my_spawn2(Mod, Func, Args) -> 64 | Pid = spawn(Mod, Func, Args), 65 | Born = erlang:system_time(millisecond), 66 | on_exit(Pid, fun(Why) -> 67 | Died = erlang:system_time(millisecond), 68 | io:format("~p lived for ~p ms, died with: ~p~n", [Pid, Died - Born, Why]) 69 | end). 70 | 71 | %% 72 | %% Exercise 3. 73 | %% Write a function my_spawn(Mod, Func, Args, Time) that behaves like spawn(Mod, 74 | %% Func, Args) but with one difference. If the spawned process lives for more 75 | %% than Time seconds, it should be killed. 76 | %% 77 | %% > prog_erl:my_spawn(prog_erl, die_after, [7000], 5). 78 | %% 79 | my_spawn(Mod, Func, Args, Time) -> 80 | spawn(fun() -> 81 | {Pid, Ref} = spawn_monitor(Mod, Func, Args), 82 | receive 83 | {'DOWN', Ref, process, Pid, Why} -> Why 84 | after Time * 1000 -> 85 | io:format("~p is about to die~n", [Pid]), 86 | exit(Pid, too_old) 87 | end 88 | end). 89 | 90 | %% 91 | %% Exercise 4. 92 | %% Write a function that creates a registered process that writes out "I'm still 93 | %% running" every five seconds. Write a function that monitors this process and 94 | %% restarts it if it dies. Start the global process and the monitor process. 95 | %% Kill the global process and check that it has been restarted by the monitor. 96 | %% 97 | %% > prog_erl:keep_alive(ch_13_4, fun prog_erl:ch_13_4/0). 98 | %% > whereis(ch_13_4). 99 | %% > exit(whereis(ch_13_4), die). 100 | %% > whereis(ch_13_4). 101 | %% 102 | keep_alive(Name, Fun) -> 103 | register(Name, Pid = spawn(Fun)), 104 | on_exit(Pid, fun(_Why) -> keep_alive(Name, Fun) end). 105 | 106 | ch_13_4() -> 107 | io:format("I'm still running~n"), 108 | receive 109 | after 5000 -> ch_13_4() 110 | end. 111 | 112 | %% 113 | %% Exercise 5. 114 | %% Write a function that starts and monitors several worker processes. If any 115 | %% of the worker processes dies abnormally, restart it. 116 | %% 117 | %% > prog_erl:ch_13_5([3000, 7000]). 118 | %% 119 | keep_alive(Fun) -> 120 | on_exit(spawn(Fun), fun(_Why) -> keep_alive(Fun) end). 121 | 122 | ch_13_5([]) -> ok; 123 | ch_13_5([M | Ms]) -> 124 | keep_alive(fun() -> die_after(M) end), 125 | ch_13_5(Ms). 126 | 127 | %% 128 | %% Exercise 6. 129 | %% Write a function that starts and monitors several worker processes. If any 130 | %% of the worker processes dies abnormally, kill all the worker processes and 131 | %% restart them all. 132 | %% 133 | %% > prog_erl:ch_13_6(). 134 | %% 135 | ch_13_6() -> 136 | ch_13_6([ 137 | fun() -> die_after(3000) end, 138 | fun() -> die_after(5000) end, 139 | fun() -> die_after(7000) end 140 | ]). 141 | 142 | ch_13_6(Funs) -> 143 | ch_13_6(Funs, [spawn_monitor(F) || F <- Funs]). 144 | 145 | ch_13_6(Funs, Procs) -> 146 | receive 147 | {'DOWN', Ref, process, Pid, Why} -> 148 | io:format("~p died with: ~p~n", [Pid, Why]), 149 | lists:foreach( 150 | fun({P, R} = Proc) -> 151 | demonitor(R), 152 | io:format("Killing ~p~n", [Proc]), 153 | exit(P, kill_all) 154 | end, 155 | lists:delete({Pid, Ref}, Procs)), 156 | ch_13_6(Funs) 157 | end. -------------------------------------------------------------------------------- /lib/examples/src/recursion.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @reference [CT1], p.84. 3 | %% Exercise 3-5: Manipulating lists 4 | %% Exercise 3-6: Sorting lists 5 | %% 6 | -module(recursion). 7 | -export([average/1, 8 | bump/1, 9 | concatenate/1, 10 | create/1, create/2, 11 | create_r/1, create_r/2, 12 | filter/2, 13 | flatten/1, 14 | merge/2, 15 | msort/1, 16 | print/1, 17 | qsort/1, pqsort/1, pqsort_loop/2, 18 | reverse/1, 19 | sum/1, sum/2]). 20 | 21 | bump(List) -> bump_acc(List, []). 22 | bump_acc([], Acc) -> Acc; 23 | bump_acc([H | T], Acc) -> bump_acc(T, Acc ++ [H + 1]). 24 | 25 | average(List) -> average_acc(List, 0, 0). 26 | average_acc([], _, 0) -> 0; 27 | average_acc([], Sum, Len) -> Sum / Len; 28 | average_acc([H | T], Sum, Len) -> average_acc(T, Sum + H, Len + 1). 29 | 30 | sum(N) -> sum_acc(1, N, 0). 31 | sum(N, M) -> sum_acc(N, M, 0). 32 | sum_acc(N, M, Sum) when N < M -> sum_acc(N, M - 1, Sum + M); 33 | sum_acc(N, N, Sum) -> Sum + N. 34 | 35 | create(N) -> create(1, N). 36 | create(N, M) -> create_acc(N, M, []). 37 | create_acc(N, M, List) when N < M -> create_acc(N, M - 1, [M | List]); 38 | create_acc(N, N, List) -> [N | List]. 39 | 40 | create_r(N) -> create_r(1, N). 41 | create_r(N, M) -> create_r_acc(N, M, []). 42 | create_r_acc(N, M, List) when N < M -> create_r_acc(N + 1, M, [N | List]); 43 | create_r_acc(M, M, List) -> [M | List]. 44 | 45 | print(N) when 0 < N -> io:format("Number: ~p~n", [N]), print(N - 1); 46 | print(N) when N < 0 -> io:format("Number: ~p~n", [N]), print(N + 1); 47 | print(0) -> io:format("Number: ~p~n", [0]). 48 | 49 | filter(List, N) -> filter_acc(List, N, []). 50 | filter_acc([], _, Acc) -> Acc; 51 | filter_acc([M | Tail], N, Acc) when M =< N -> filter_acc(Tail, N, Acc ++ [M]); 52 | filter_acc([_ | Tail], N, Acc) -> filter_acc(Tail, N, Acc). 53 | 54 | reverse([]) -> []; 55 | reverse([X | Tail]) -> reverse(Tail) ++ [X]. 56 | 57 | concatenate([]) -> []; 58 | concatenate([X | Tail]) -> X ++ concatenate(Tail). 59 | 60 | flatten([]) -> []; 61 | flatten([H | T]) -> flatten(H) ++ flatten(T); 62 | flatten(X) -> [X]. 63 | 64 | merge(Xs, Ys) -> lists:reverse(mergeL(Xs, Ys, [])). 65 | mergeL([X | Xs], Ys, Zs) -> mergeR(Xs, Ys, [X | Zs]); 66 | mergeL([], [], Zs) -> Zs. 67 | mergeR(Xs, [Y | Ys], Zs) -> mergeL(Xs, Ys, [Y | Zs]); 68 | mergeR([], [], Zs) -> Zs. 69 | 70 | qsort([]) -> []; 71 | qsort([H | Tail]) -> qsort([Y || Y <- Tail, Y < H]) ++ [H] ++ qsort([Y || Y <- Tail, Y >= H]). 72 | 73 | % Parallel Quicksort. 74 | pqsort([]) -> []; 75 | pqsort([H | Tail]) -> 76 | Left = spawn(?MODULE, pqsort_loop, [self(), [Y || Y <- Tail, Y < H]]), 77 | Right = spawn(?MODULE, pqsort_loop, [self(), [Y || Y <- Tail, Y >= H]]), 78 | receive 79 | {Left, LSorted} -> ok 80 | end, 81 | receive 82 | {Right, RSorted} -> ok 83 | end, 84 | LSorted ++ [H | RSorted]. 85 | 86 | pqsort_loop(From, List) -> 87 | case List of 88 | [] -> From ! {self(), List}; 89 | _ -> From ! {self(), pqsort(List)} 90 | end. 91 | 92 | % http://en.wikipedia.org/wiki/Merge_sort 93 | msort([]) -> []; 94 | msort([X]) -> [X]; 95 | msort(List) -> 96 | {Left, Right} = lists:split(trunc(length(List) / 2), List), 97 | merge_ordered(msort(Left), msort(Right)). 98 | 99 | merge_ordered(Left, Right) -> merge_ordered_acc(Left, Right, []). 100 | merge_ordered_acc([], [], Acc) -> Acc; 101 | merge_ordered_acc(Left, [], Acc) -> Acc ++ Left; 102 | merge_ordered_acc([], Right, Acc) -> Acc ++ Right; 103 | merge_ordered_acc([X | Xs], [Y | Ys], Acc) when X < Y -> merge_ordered_acc(Xs, [Y | Ys], Acc ++ [X]); 104 | merge_ordered_acc([X | Xs], [Y | Ys], Acc) -> merge_ordered_acc([X | Xs], Ys, Acc ++ [Y]). 105 | -------------------------------------------------------------------------------- /lib/examples/src/ring.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Ring benchmark. 3 | %% 4 | %% The shell process is the first in the ring. 5 | %% It orchestrates the ring by sending internal 6 | %% messages as well as the given message. 7 | %% ``` 8 | %% 1> ring:start(2, 5, message).''' 9 | %% 10 | %% @reference [CT1] Exercise 4-2, p.115 11 | %% @see ring2 12 | %% 13 | -module(ring). 14 | -export([start/3]). 15 | -export([create/4]). 16 | 17 | -ifndef(no_trace). 18 | -define(TRACE(P, M, Message), io:format("~p -> ~p: ~p (~p)~n", [self(), P, Message, M])). 19 | -else. 20 | -define(TRACE(P, M, Message), void). 21 | -endif. 22 | 23 | start(N, M, Message) -> 24 | create(undef, N - 1, M, Message). 25 | 26 | create(Parent, 0, M, Message) -> 27 | Parent ! {created, self()}, 28 | evaluate(Parent, M, Message); 29 | 30 | create(Parent, N, M, Message) -> 31 | spawn(?MODULE, create, [self(), N - 1, M, Message]), % ignore PID 32 | evaluate(Parent, M, Message). 33 | 34 | evaluate(undef, M, Message) -> 35 | receive 36 | {created, Shell} -> 37 | Shell ! Message, 38 | ?TRACE(Shell, M, Message), 39 | evaluate(Shell, M - 1, Message) 40 | end; 41 | 42 | evaluate(Parent, 0, _) -> 43 | receive 44 | _ -> Parent ! stop 45 | end; 46 | 47 | evaluate(Parent, M, Message) -> 48 | receive 49 | {created, Shell} -> 50 | Parent ! {created, Shell}, 51 | evaluate(Parent, M, Message); 52 | Message -> 53 | Parent ! Message, 54 | ?TRACE(Parent, M, Message), 55 | evaluate(Parent, M - 1, Message) 56 | end. 57 | -------------------------------------------------------------------------------- /lib/examples/src/ring2.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Ring benchmark. 3 | %% 4 | %% This version implements functional algorithm: 5 | %% fold over sequence of natural numbers `1..N' 6 | %% to build a circular linked list of processes. 7 | %% The shell process is not a part of the ring. 8 | %% 9 | %% Compile with trace disabled: 10 | %% ``` 11 | %% 1> c(ring2, {d, no_trace}). 12 | %% 2> ring2:measure_time(10000, 10000, hello).''' 13 | %% 14 | %% @reference [A2] Exercise 12.3, p. 198. 15 | %% @see ring 16 | %% 17 | -module(ring2). 18 | -export([measure_time/3, start/3]). 19 | 20 | -ifndef(no_trace). 21 | -define(TRACE(P, M, Message), io:format("~p -> ~p: ~p (~p)~n", [self(), P, Message, M])). 22 | -else. 23 | -define(TRACE(P, M, Message), void). 24 | -endif. 25 | 26 | measure_time(N, M, Message) -> 27 | {Micros, ok} = timer:tc(?MODULE, start, [N, M, Message]), 28 | io:format("Done in ~p µs. Message passing speed ~p s~n", [Micros, Micros / N / M / 1000000.0]). 29 | 30 | %% 31 | %% @doc Creates N processes in a ring. 32 | %% Sends a message round the ring M times 33 | %% so that a total of N*M messages get sent. 34 | %% 35 | start(N, M, Message) when N > 1 -> 36 | {_Pid, MonitorRef} = spawn_monitor(fun() -> ring(N - 1, M, Message) end), 37 | receive 38 | {_Tag, MonitorRef, _Type, _Object, _Info} -> ok 39 | end. 40 | 41 | ring(N, M, Message) when M > 0 -> 42 | P = lists:foldl(fun(_, Proc) -> spawn(fun() -> loop(Proc) end) end, self(), lists:seq(1, N)), 43 | ?TRACE(P, 1, Message), 44 | P ! {message, M, Message}, 45 | loop(P, M). 46 | 47 | loop(P) -> 48 | receive 49 | {message, M, Message} -> 50 | ?TRACE(P, M, Message), 51 | P ! {message, M, Message}, 52 | loop(P); 53 | stop -> 54 | P ! stop 55 | end. 56 | 57 | loop(P, 1) -> P ! stop; 58 | loop(P, M) -> 59 | receive 60 | {message, M, Message} -> 61 | ?TRACE(P, M, Message), 62 | P ! {message, M - 1, Message}, 63 | loop(P, M - 1) 64 | end. 65 | -------------------------------------------------------------------------------- /lib/examples/src/trees.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @reference [CT1], p.169. 3 | %% Exercise 7-5: Binary Trees 4 | %% 5 | -module(trees). 6 | -export([sum/1, max/1, is_ordered/1, insert/2]). 7 | -include("Trees.hrl"). 8 | 9 | sum(Tree) -> 10 | case Tree of 11 | undefined -> 12 | 0; 13 | #bintree{value = Val, left = L, right = R} -> 14 | Val + sum(L) + sum(R) 15 | end. 16 | 17 | max(Tree) -> max_help(0, Tree). 18 | 19 | max_help(Result, Tree) -> 20 | case Tree of 21 | undefined -> 22 | Result; 23 | #bintree{value = Val, left = L, right = R} -> 24 | NewResult = erlang:max(Result, Val), 25 | erlang:max(max_help(NewResult, L), max_help(NewResult, R)) 26 | end. 27 | 28 | is_ordered(Tree) -> 29 | case Tree of 30 | undefined -> 31 | true; 32 | #bintree{value = Val, left = L, right = R} -> 33 | (max(L) < Val) and (Val =< max(R)) 34 | end. 35 | 36 | insert(Value, Tree) -> 37 | case Tree of 38 | undefined -> 39 | #bintree{value = Value}; 40 | #bintree{value = Val, left = L, right = R} when Value < Val -> 41 | #bintree{value = Val, left = insert(Value, L), right = R}; 42 | #bintree{value = Val, left = L, right = R} -> 43 | #bintree{value = Val, left = L, right = insert(Value, R)} 44 | end. 45 | -------------------------------------------------------------------------------- /lib/examples/test/recursion_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @reference [CT1], p.420. 3 | %% Exercise 19-1: Testing sequential functions 4 | %% 5 | -module(recursion_tests). 6 | -include_lib("eunit/include/eunit.hrl"). 7 | 8 | bump_test() -> 9 | ?assertEqual([2, 3, 4], recursion:bump([1, 2, 3])). 10 | 11 | average_test_() -> [ 12 | ?_assertEqual(2.5, recursion:average([1, 2, 3, 4])), 13 | ?_assertEqual(2.0, recursion:average([1, 2, 3])) 14 | ]. 15 | 16 | sum1_test() -> 17 | ?assertEqual(10, recursion:sum(4)). 18 | 19 | sum2_test() -> 20 | ?assertEqual(12, recursion:sum(3, 5)). 21 | 22 | create1_test() -> 23 | ?assertEqual([1, 2, 3, 4], recursion:create(4)). 24 | 25 | create2_test() -> 26 | ?assertEqual([3, 4, 5], recursion:create(3, 5)). 27 | 28 | filter_test() -> 29 | ?assertEqual([1, 2, 3], recursion:filter([1, 2, 3, 4, 5], 3)). 30 | 31 | reverse_test() -> 32 | ?assertEqual([5, 4, 3, 2, 1], recursion:reverse([1, 2, 3, 4, 5])). 33 | 34 | concatenate_test() -> 35 | ?assertEqual([1, 2, 3, 4, 5, 6], recursion:concatenate([[1, 2, 3], [4], [5, 6]])). 36 | 37 | flatten_test() -> 38 | ?assertEqual([1, 2, 3, 4, 5, 6], recursion:flatten([[1, [2, [3], []]], [[[4]]], [5, 6]])). 39 | 40 | merge_test_() -> [ 41 | ?_assertEqual([1, 2, 3, 4], recursion:merge([1, 3], [2, 4])), 42 | ?_assertEqual([1, 2, 3], recursion:merge([1, 3], [2])) 43 | ]. 44 | 45 | qsort_test() -> 46 | ?assertEqual([1, 2, 3, 4, 5, 6], recursion:qsort([1, 6, 2, 5, 3, 4])). 47 | 48 | pqsort_test() -> 49 | ?assertEqual([1, 2, 3, 4, 5, 6], recursion:pqsort([1, 6, 2, 5, 3, 4])). 50 | 51 | msort_test_() -> [ 52 | ?_assertEqual([], recursion:msort([])), 53 | ?_assertEqual([1], recursion:msort([1])), 54 | ?_assertEqual([3, 9, 10, 27, 38, 43, 82], recursion:msort([38, 27, 43, 3, 9, 82, 10])) 55 | ]. 56 | -------------------------------------------------------------------------------- /lib/examples/test/trees_test.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @reference [CT1], p.169. 3 | %% Exercise 7-5: Binary Trees 4 | %% 5 | -module(trees_test). 6 | -include("Trees.hrl"). 7 | -include_lib("eunit/include/eunit.hrl"). 8 | 9 | sum_undefined_tree_test() -> 10 | ?assertEqual(0, trees:sum(undefined)). 11 | 12 | sum_balanced_tree_test() -> 13 | Tree = #bintree{value = 5, left = #bintree{value = 6}, right = #bintree{value = 9}}, 14 | ?assertEqual(20, trees:sum(Tree)). 15 | 16 | sum_lined_tree_test() -> 17 | Tree = #bintree{value = 5, left = #bintree{value = 6, left = #bintree{value = 9}}}, 18 | ?assertEqual(20, trees:sum(Tree)). 19 | 20 | max_undefined_tree_test() -> 21 | ?assertEqual(0, trees:max(undefined)). 22 | 23 | max_balanced_tree_test() -> 24 | Tree = #bintree{value = 5, left = #bintree{value = 6}, right = #bintree{value = 9}}, 25 | ?assertEqual(9, trees:max(Tree)). 26 | 27 | max_lined_tree_test() -> 28 | Tree = #bintree{value = 5, left = #bintree{value = 6, left = #bintree{value = 9}}}, 29 | ?assertEqual(9, trees:max(Tree)). 30 | 31 | is_ordered_undefined_tree_test() -> 32 | ?assert(trees:is_ordered(undefined)). 33 | 34 | is_ordered_false_test() -> 35 | Tree = #bintree{value = 5, left = #bintree{value = 6}, right = #bintree{value = 9}}, 36 | ?assertNot(trees:is_ordered(Tree)). 37 | 38 | is_ordered_true_test() -> 39 | Tree = #bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6}}, 40 | ?assert(trees:is_ordered(Tree)). 41 | 42 | insert_into_undefined_test() -> 43 | ?assertEqual(#bintree{value = 5}, trees:insert(5, undefined)). 44 | 45 | insert_into_left_tree_test() -> 46 | Tree = #bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6}}, 47 | ?assertEqual(#bintree{value = 5, left = #bintree{value = 4, left = #bintree{value = 3}}, right = #bintree{value = 6}}, 48 | trees:insert(3, Tree)). 49 | 50 | insert_equal_into_right_tree_test() -> 51 | Tree = #bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6}}, 52 | ?assertEqual(#bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6, left = #bintree{value = 5}}}, 53 | trees:insert(5, Tree)). 54 | 55 | insert_greater_into_right_tree_test() -> 56 | Tree = #bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6}}, 57 | ?assertEqual(#bintree{value = 5, left = #bintree{value = 4}, right = #bintree{value = 6, right = #bintree{value = 7}}}, 58 | trees:insert(7, Tree)). 59 | -------------------------------------------------------------------------------- /lib/mastermind/README.md: -------------------------------------------------------------------------------- 1 | # Mastermind 2 | 3 | Erlang implementation of [Matermind](http://en.wikipedia.org/wiki/Mastermind_(board_game)) game. 4 | 5 | ## Build 6 | 7 | $ rebar3 compile 8 | 9 | ## Play 10 | 11 | $ rebar3 shell 12 | 13 | 1> codemaker:start(). 14 | true 15 | 2> codemaker:guess([1,1,2,2]). 16 | {0,0} 17 | -------------------------------------------------------------------------------- /lib/mastermind/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /lib/mastermind/rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /lib/mastermind/src/codemaker.erl: -------------------------------------------------------------------------------- 1 | -module(codemaker). 2 | 3 | %% API 4 | -export([start/0, guess/1]). 5 | -export([loop/1]). 6 | 7 | %%%=================================================================== 8 | %%% API 9 | %%%=================================================================== 10 | 11 | %% @doc Generates random code and starts listening for guesses. 12 | %% Registers a new process which dies after the correct guess. 13 | -spec start() -> true. 14 | start() -> 15 | register(?MODULE, spawn(?MODULE, loop, [position_list(random_row())])). 16 | 17 | random_row() -> 18 | [r(), r(), r(), r()]. 19 | 20 | r() -> 21 | rand:uniform(6). 22 | 23 | position_list(Row) -> 24 | dict:to_list(positions(Row)). 25 | 26 | positions(List) -> 27 | positions(List, 1, dict:new()). 28 | positions([X|Xs], Pos, Acc) -> 29 | positions(Xs, Pos + 1, dict:append(X, Pos, Acc)); 30 | positions([], _, Acc) -> 31 | dict:map(fun(_, V) -> sets:from_list(V) end, Acc). 32 | 33 | 34 | %% @doc Internal function: Starts a listener loop. 35 | %% Responds on messages in the form of [pos_integer()]. 36 | -spec loop([{pos_integer(), [pos_integer()]}]) -> {Black::non_neg_integer(), White::non_neg_integer()} | congrats. 37 | loop(Code) -> 38 | receive 39 | {Pid, Guess} -> 40 | case score(Code, Guess) of 41 | {4, 0} -> 42 | Pid ! congrats; 43 | Score -> 44 | Pid ! Score, 45 | loop(Code) 46 | end 47 | end. 48 | 49 | score(Code, Guess) -> 50 | score(Code, positions(Guess), {0, 0}). 51 | 52 | score([{C,CPs}|Xs], Guess, {B, W}) -> 53 | case dict:find(C, Guess) of 54 | {ok, GPs} -> 55 | {NewB, NewW} = match(CPs, GPs), 56 | score(Xs, Guess, {B + NewB, W + NewW}); 57 | _ -> 58 | score(Xs, Guess, {B, W}) 59 | end; 60 | score([], _, Acc) -> 61 | Acc. 62 | 63 | match(Set1, Set2) -> 64 | B = sets:size(sets:intersection(Set1, Set2)), 65 | W = min(sets:size(Set1) - B, sets:size(Set2) - B), 66 | {B, W}. 67 | 68 | 69 | %% @doc Submits a guess pattern to the codemaker. 70 | %% Returns a tuple of black and white pegs. 71 | -spec guess([pos_integer()]) -> {Black::non_neg_integer(), White::non_neg_integer()} | congrats. 72 | guess(Pattern) -> 73 | ?MODULE ! {self(), Pattern}, 74 | receive 75 | Feedback -> Feedback 76 | end. 77 | 78 | %%%=================================================================== 79 | %%% Test Functions 80 | %%%=================================================================== 81 | 82 | -ifdef(TEST). 83 | -include_lib("eunit/include/eunit.hrl"). 84 | 85 | sc(Code, Guess, Result) -> 86 | ?assertEqual(Result, score(position_list(Code), Guess)). 87 | 88 | score_test() -> 89 | sc([1,2,3,4], [5,5,2,2], {0,1}), 90 | sc([1,2,3,4], [4,3,3,5], {1,1}), 91 | sc([1,2,3,4], [1,4,1,5], {1,1}), 92 | sc([1,2,3,4], [6,6,4,5], {0,1}), 93 | sc([1,2,3,4], [2,3,1,4], {1,3}), 94 | sc([1,2,3,4], [1,3,2,4], {2,2}), 95 | sc([1,2,3,4], [1,2,3,4], {4,0}). 96 | 97 | -endif. 98 | -------------------------------------------------------------------------------- /lib/mastermind/src/mastermind.app.src: -------------------------------------------------------------------------------- 1 | {application, mastermind, 2 | [{description, "Erlang implementation of the Matermind game"}, 3 | {vsn, "1.0.0"}, 4 | {modules, []}, 5 | {registered, []}, 6 | {applications, [kernel, stdlib]}, 7 | {maintainers, ["Andrey Paramonov"]}, 8 | {licenses, ["Apache 2.0"]}, 9 | {links, [{"GitHub", "https://github.com/ndpar/erlang/tree/master/lib/mastermind"}]} 10 | ]}. 11 | -------------------------------------------------------------------------------- /lib/ndpar/doc/overview.edoc: -------------------------------------------------------------------------------- 1 | @author Andrey V. Paramonov 2 | @since November 2009 3 | 4 | @reference [A2] Armstrong J. Programming Erlang (2nd edition) 5 | @reference [B1] Bird R. Pearls of Functional Algorithm Design 6 | @reference [CLRS3] Cormen T.H., Leiserson C.E., Rivest R.L., Stein C. Introduction to Algorithms (3rd edition) 7 | @reference [CT1] Cesarini F., Thompson S. Erlang Programming 8 | @reference [FSK1] Ferguson N., Schneier B., Kohno T. Cryptography Engineering 9 | @reference [LMC1] Logan M., Merritt E., Carlsson R. Erlang and OTP in Action 10 | @reference [MvOV1] Menezes A.J., van Oorschot P.C., Vanstone S.A. Handbook of Applied Cryptography 11 | -------------------------------------------------------------------------------- /lib/ndpar/rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. -------------------------------------------------------------------------------- /lib/ndpar/rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /lib/ndpar/src/bin.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Various functions to work on binaries. 3 | %% 4 | %% @reference [A2] Chapter 7 5 | %% 6 | -module(bin). 7 | -export([integer_to_bitstring/1]). 8 | -export([lxor/1, lxor/2]). 9 | -export([bin_to_hexstr/1, hexstr_to_bin/1]). 10 | -export([reverse_bytes/1, reverse_bits/1]). 11 | -export([term_to_packet/1, packet_to_term/1]). 12 | 13 | 14 | %% 15 | %% @doc Returns a bitstring representation of the 16 | %% given integer with leading zeroes removed. 17 | %% 18 | %% ```<<2#1100:4>> = bin:integer_to_bitstring(12).''' 19 | %% 20 | %% @see binary:encode_unsigned/1. 21 | %% 22 | -spec integer_to_bitstring(non_neg_integer()) -> bitstring(). 23 | 24 | integer_to_bitstring(Int) -> trim_bitstring(binary:encode_unsigned(Int)). 25 | 26 | trim_bitstring(<<>>) -> <<>>; 27 | trim_bitstring(<<1:1, _/bitstring>> = B) -> B; 28 | trim_bitstring(<<0:1, B/bitstring>>) -> trim_bitstring(B). 29 | 30 | %% 31 | %% @doc Left XOR. 32 | %% 33 | %% ```<<16#B9F9:16>> = bin:lxor(<<16#ABCDEF:24>>, <<16#1234:16>>).''' 34 | %% 35 | -spec lxor(binary(), binary()) -> binary(). 36 | 37 | lxor(X, Y) -> 38 | Length = min(size(X), size(Y)), 39 | crypto:exor(binary:part(X, 0, Length), binary:part(Y, 0, Length)). 40 | 41 | %% 42 | %% @doc Left XOR. 43 | %% Can be used from escript or shell. 44 | %% 45 | -spec lxor([string()]) -> string(). 46 | 47 | lxor([H | T]) -> 48 | F = fun(X, Acc) -> lxor(hexstr_to_bin(X), Acc) end, 49 | bin_to_hexstr(lists:foldl(F, hexstr_to_bin(H), T)). 50 | 51 | %% 52 | %% @doc Converts hexadecimal string to binary. 53 | %% The string length is expected to be even. 54 | %% 55 | -spec hexstr_to_bin(string()) -> binary(). 56 | 57 | hexstr_to_bin(String) -> 58 | 0 = length(String) rem 2, 59 | <<1:8, Bin/binary>> = binary:encode_unsigned(list_to_integer([$1 | String], 16)), 60 | Bin. 61 | 62 | %% 63 | %% @doc Converts binary to hexadecimal string. 64 | %% 65 | -spec bin_to_hexstr(binary()) -> string(). 66 | 67 | bin_to_hexstr(Bin) -> 68 | binary_to_list(<<<> || <> <= Bin, Y <- integer_to_list(X, 16)>>). 69 | 70 | %% 71 | %% @doc Reverses the order of bytes in a binary. 72 | %% 73 | -spec reverse_bytes(binary()) -> binary(). 74 | 75 | reverse_bytes(B) -> 76 | list_to_binary(lists:reverse(binary_to_list(B))). 77 | 78 | %% 79 | %% @doc Reverses the bits in a binary. 80 | %% 81 | -spec reverse_bits(binary()) -> binary(). 82 | 83 | reverse_bits(B) -> reverse_bits(B, <<>>). 84 | 85 | reverse_bits(<<>>, Acc) -> Acc; 86 | reverse_bits(<>, Acc) -> 87 | reverse_bits(Rest, <>). 88 | 89 | 90 | %% 91 | %% @doc Returns a binary consisting of a 4-byte length header N 92 | %% followed by N bytes of data produced by calling 93 | %% `term_to_binary(Term)'. 94 | %% @see packet_to_term/1 95 | %% 96 | -spec term_to_packet(term()) -> binary(). 97 | 98 | term_to_packet(Term) -> 99 | <> = term_to_binary(Term), 100 | Length = size(Data), 101 | <>. 102 | 103 | %% 104 | %% @doc The inverse of the {@link term_to_packet} function. 105 | %% 106 | -spec packet_to_term(B::binary()) -> term(). 107 | 108 | packet_to_term(<>) -> 109 | binary_to_term(<>). 110 | 111 | 112 | %% ============================================================================= 113 | %% Unit tests 114 | %% ============================================================================= 115 | 116 | -include_lib("eunit/include/eunit.hrl"). 117 | 118 | integer_to_bitstring_test() -> 119 | ?assertEqual(<<>>, integer_to_bitstring(0)), 120 | ?assertEqual(<<2#10001100:8, 0:2>>, integer_to_bitstring(560)). 121 | 122 | hexstr_to_bin_test() -> 123 | ?assertEqual(<<0, 18, 52, 86, 120, 144, 171, 205, 239>>, hexstr_to_bin("001234567890ABCDEF")). 124 | 125 | bin_to_hexstr_test() -> 126 | ?assertEqual("001234567890ABCDEF", bin_to_hexstr(<<0, 18, 52, 86, 120, 144, 171, 205, 239>>)). 127 | 128 | lxor_test() -> 129 | ?assertEqual("94D5513AC50DFF660F9FDE299DF35718", 130 | lxor(["E20106D7CD0DF0761E8DCD3D88E54000", "76D457ED08000F101112131415161718"])), 131 | ?assertEqual("94D5513AC50D", 132 | lxor(["E20106D7CD0DF0761E8DCD3D88E54000", "76D457ED0800"])). 133 | 134 | 135 | reverse_bytes_test() -> 136 | ?assertEqual(<<12, 34, 56, 78>>, reverse_bytes(<<78, 56, 34, 12>>)). 137 | 138 | reverse_bits_test() -> 139 | ?assertEqual(<<2#1001100100010111:16>>, reverse_bits(<<2#1110100010011001:16>>)). 140 | 141 | term_to_packet_test() -> 142 | ?assertEqual(<<131, 104, 3, 100, 0, 0, 0, 17, 0, 4, 97, 116, 111, 109, 97, 5, 107, 0, 6, 115, 116, 114, 105, 110, 103>>, 143 | term_to_packet({atom, 5, "string"})). 144 | 145 | packet_to_term_test() -> 146 | ?assertEqual({atom, 5, "string"}, 147 | packet_to_term(<<131, 104, 3, 100, 0, 0, 0, 17, 0, 4, 97, 116, 111, 109, 97, 5, 107, 0, 6, 115, 116, 114, 105, 110, 103, "garbage">>)). 148 | -------------------------------------------------------------------------------- /lib/ndpar/src/celebrities.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc A problem of finding celebrities. 3 | %% 4 | %% Imaging a set `P' of people at a party. 5 | %% Say a subset `C' of `P' forms a celebrity clique if 6 | %% `C' is nonempty, everybody at the party knows every member 7 | %% of `C', but members of `C' know only each other. 8 | %% Find this celebrity clique, assuming it exists. 9 | %% Data for the problem: 10 | %% 1) binary predicate `knows' and 11 | %% 2) the set `P' as a list without duplicates. 12 | %% 13 | %% Note: the problem is not to determine whether or not such a clique exists. 14 | %% 15 | %% What is interesting about this problem is that it is asymptotically more 16 | %% efficient to find a solution assuming one exists than to check that 17 | %% it actually is a solution. 18 | %% 19 | %% @reference [B1] Chapter 9, pp. 56–63 20 | %% 21 | -module(celebrities). 22 | -author("Andrey Paramonov "). 23 | 24 | -export([cclique/2]). 25 | 26 | -import(lists, [foldr/3]). 27 | 28 | %% 29 | %% @doc Finds celebrity clique in linear time 30 | %% assuming it exists. 31 | %% If the clique does not exist, returns meaningless list. 32 | %% 33 | -spec cclique(Knows, [A]) -> [A] when 34 | Knows :: fun((A, A) -> boolean()), 35 | A :: term(). 36 | 37 | cclique(Knows, Ps) -> 38 | Op = fun(P, Cs) -> op(Knows, P, Cs) end, 39 | foldr(Op, [], Ps). 40 | 41 | op(_, P, []) -> [P]; 42 | op(Knows, P, [C | _] = Cs) -> 43 | case {Knows(P, C), Knows(C, P)} of 44 | {false, _} -> [P]; 45 | {_, false} -> Cs; 46 | _ -> [P | Cs] 47 | end. 48 | 49 | %% ============================================================================= 50 | %% Unit tests 51 | %% ============================================================================= 52 | 53 | -ifdef(TEST). 54 | -include_lib("eunit/include/eunit.hrl"). 55 | 56 | knows(_, {c, _}) -> true; 57 | knows({c, _}, {p, _}) -> false; 58 | knows({p, M}, {p, N}) when M < N -> true; 59 | knows({p, _}, {p, _}) -> false. 60 | 61 | clique_exists_test_() -> 62 | [ 63 | ?_assertEqual( 64 | [{c, 1}, {c, 4}, {c, 3}], 65 | cclique(fun knows/2, [{c, 1}, {p, 6}, {c, 4}, {p, 2}, {p, 5}, {c, 3}])) 66 | ]. 67 | 68 | clique_doesnt_exist_test_() -> 69 | [ 70 | ?_assertEqual( 71 | [{p, 1}], % this is not a celebrity clique 72 | cclique(fun knows/2, [{p, 1}, {p, 1}])) 73 | ]. 74 | 75 | -endif. 76 | -------------------------------------------------------------------------------- /lib/ndpar/src/century.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc The problem of making a century is to list all the ways 3 | %% the operations + and * can be inserted into the list of 4 | %% digits `[1..9]' so as to make a total of 100. 5 | %% Two such ways are: 6 | %% ``` 7 | %% 100 = 12 + 34 + 5 * 6 + 7 + 8 + 9 8 | %% 100 = 1 + 2 * 3 + 4 + 5 + 67 + 8 + 9 9 | %% ''' 10 | %% Usage: 11 | %% ``` 12 | %% 1> lists:map(fun(S) -> century:format(S) end, century:solutions(100, [1,2,3,4,5,6,7,8,9])). 13 | %% 2> lists:map(fun(S) -> century:format(S) end, century:solutions(1000, [3,1,4,1,5,9,2,6,5,3,5,8,9,7])). 14 | %% ''' 15 | %% @reference [B1] Chapter 6, pp. 33–40 16 | %% 17 | -module(century). 18 | -author("Andrey Paramonov "). 19 | 20 | -export([solutions/2]). 21 | -export([format/1]). 22 | 23 | -import(lists, [concat/1, filter/2, foldr/3, map/2]). 24 | 25 | -spec solutions(pos_integer(), [1..9]) -> [Solution] when 26 | Solution :: [Term], 27 | Term :: [Factor], 28 | Factor :: [1..9]. 29 | 30 | solutions(C, Digits) -> 31 | Fd = fun(X, ACC) -> expand(C, X, ACC) end, 32 | Evs = foldr(Fd, [], Digits), 33 | Ft = fun({_, B}) -> good(C, B) end, 34 | [X || {X, _} <- filter(Ft, Evs)]. 35 | 36 | expand(_, X, []) -> [{[[[X]]], {10, X, 1, 0}}]; 37 | expand(C, X, Evs) -> 38 | Filter = fun({_, B}) -> ok(C, B) end, 39 | F = fun(E) -> filter(Filter, glue(X, E)) end, 40 | concat(map(F, Evs)). 41 | 42 | good(C, {_, F, T, E}) -> F * T + E =:= C. 43 | ok(C, {_, F, T, E}) -> F * T + E =< C. 44 | 45 | glue(X, {[[Xs | Xss] | Xsss], {K, F, T, E}}) -> [ 46 | {[[[X | Xs] | Xss] | Xsss], {10 * K, K * X + F, T, E}}, 47 | {[[[X], Xs | Xss] | Xsss], {10, X, F * T, E}}, 48 | {[[[X]], [Xs | Xss] | Xsss], {10, X, 1, F * T + E}} 49 | ]. 50 | 51 | format(Solution) -> 52 | Terms = map(fun formatt/1, Solution), 53 | string:join(Terms, "+"). 54 | 55 | formatt(Term) -> 56 | Factors = map(fun formatf/1, Term), 57 | string:join(Factors, "*"). 58 | 59 | formatf(Factor) -> [X + 48 || X <- Factor]. 60 | 61 | %% ============================================================================= 62 | %% Unit tests 63 | %% ============================================================================= 64 | 65 | -ifdef(TEST). 66 | -include_lib("eunit/include/eunit.hrl"). 67 | 68 | glue_test_() -> 69 | [ 70 | ?_assertEqual( 71 | [ 72 | {[[[2, 1]]], {100, 21, 1, 0}}, 73 | {[[[2], [1]]], {10, 2, 1, 0}}, 74 | {[[[2]], [[1]]], {10, 2, 1, 1}} 75 | ], 76 | glue(2, {[[[1]]], {10, 1, 1, 0}})) 77 | ]. 78 | 79 | expand_test_() -> 80 | E0 = [], 81 | E1 = expand(100, 1, E0), 82 | E2 = expand(100, 2, E1), 83 | [ 84 | ?_assertEqual([{[[[1]]], {10, 1, 1, 0}}], E1), 85 | ?_assertEqual( 86 | [ 87 | {[[[2, 1]]], {100, 21, 1, 0}}, 88 | {[[[2], [1]]], {10, 2, 1, 0}}, 89 | {[[[2]], [[1]]], {10, 2, 1, 1}} 90 | ], 91 | E2 92 | ) 93 | ]. 94 | 95 | solutions_test_() -> 96 | [ 97 | ?_assertEqual( 98 | [ 99 | [[[1], [2], [3]]], % 1 * 2 * 3 100 | [[[1]], [[2]], [[3]]] % 1 + 2 + 3 101 | ], 102 | solutions(6, [1, 2, 3]) 103 | ), 104 | ?_assertEqual( 105 | [ 106 | [[[1], [2], [3]], [[4]], [[5]], [[6]], [[7]], [[8], [9]]], 107 | [[[1]], [[2]], [[3]], [[4]], [[5]], [[6]], [[7]], [[8], [9]]], 108 | [[[1], [2], [3], [4]], [[5]], [[6]], [[7], [8]], [[9]]], 109 | [[[1, 2]], [[3], [4]], [[5]], [[6]], [[7], [8]], [[9]]], 110 | [[[1]], [[2], [3]], [[4]], [[5]], [[6, 7]], [[8]], [[9]]], 111 | [[[1], [2]], [[3, 4]], [[5]], [[6], [7]], [[8]], [[9]]], 112 | [[[1, 2]], [[3, 4]], [[5], [6]], [[7]], [[8]], [[9]]] 113 | ], 114 | solutions(100, [1, 2, 3, 4, 5, 6, 7, 8, 9]) 115 | ), 116 | ?_assertEqual(202, length(solutions(1000, [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7]))) 117 | ]. 118 | 119 | format_test_() -> 120 | [ 121 | ?_assertEqual( 122 | "1*2*3+4+5+6+7+8*9", 123 | format([[[1], [2], [3]], [[4]], [[5]], [[6]], [[7]], [[8], [9]]]) 124 | ) 125 | ]. 126 | 127 | -endif. 128 | -------------------------------------------------------------------------------- /lib/ndpar/src/core.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc A collection of frequently used functions. 3 | %% Inspired by Clojure and Haskell. 4 | %% 5 | -module(core). 6 | -export([cross/2]). 7 | -export([frequencies/1, group_by/2, inc/1, min_by/2, minfree/1, msc/1, msc2/1]). 8 | -export([foldl1/2, zipfold/4, zipfold/5]). 9 | 10 | -import(lists, [filter/2, map/2, max/1]). 11 | 12 | %% 13 | %% @doc Applies pair of functions to a pair of arguments. 14 | %% 15 | -spec cross(Funs :: {F, G}, Args :: {X, Y}) -> {A, B} when 16 | F :: fun((X) -> A), 17 | G :: fun((Y) -> B), 18 | X :: term(), 19 | Y :: term(), 20 | A :: term(), 21 | B :: term(). 22 | 23 | cross({F, G}, {X, Y}) -> {F(X), G(Y)}. 24 | 25 | 26 | foldl1(_, [X]) -> X; 27 | foldl1(F, [X | Xs]) -> foldl1(F, X, Xs). 28 | 29 | foldl1(F, Accu, [Hd | Tail]) -> foldl1(F, F(Accu, Hd), Tail); 30 | foldl1(F, Accu, []) when is_function(F, 2) -> Accu. 31 | 32 | %% 33 | %% @doc Returns a map from distinct items in List to the number of times they appear. 34 | %% 35 | %% See [https://clojuredocs.org/clojure.core/frequencies] 36 | %% 37 | -spec frequencies([A]) -> #{A => pos_integer()}. 38 | 39 | frequencies(List) -> 40 | lists:foldl(fun update_count/2, #{}, List). 41 | 42 | update_count(X, Map) -> 43 | maps:update_with(X, fun(C) -> C + 1 end, 1, Map). 44 | 45 | %% 46 | %% @doc Returns an element `E' of the list with minimum value of `F(E)'. 47 | %% 48 | min_by(_, [X]) -> X; 49 | min_by(F, [X | Xs]) -> min_by(F, Xs, {X, F(X)}). 50 | 51 | min_by(_, [], {Y, _}) -> Y; 52 | min_by(F, [X | Xs], {_, Fy} = Min) -> 53 | Fx = F(X), 54 | case min(Fx, Fy) of 55 | Fx -> min_by(F, Xs, {X, Fx}); 56 | Fy -> min_by(F, Xs, Min) 57 | end. 58 | 59 | %% 60 | %% @doc Returns a map of the elements of List keyed by the result of 61 | %% Fun on each element. The value at each key will be a list of the 62 | %% corresponding elements, in the order they appeared in List. 63 | %% 64 | %% See [https://clojuredocs.org/clojure.core/group-by] 65 | %% 66 | group_by(Fun, List) -> 67 | F = fun(E, Map) -> 68 | K = Fun(E), 69 | maps:update_with(K, fun(L) -> [E | L] end, [E], Map) 70 | end, 71 | maps:map(fun(_, V) -> lists:reverse(V) end, lists:foldl(F, #{}, List)). 72 | 73 | %% 74 | %% @doc Returns a number one greater than X. 75 | %% 76 | -spec inc(X :: number()) -> number(). 77 | 78 | inc(X) -> X + 1. 79 | 80 | %% 81 | %% @doc Zips the elements of the given lists 82 | %% left folding them with the accumulator. 83 | %% 84 | -spec zipfold(F, Acc, [A], [B]) -> Acc when 85 | F :: fun((Acc, A, B) -> Acc), 86 | Acc :: term(), 87 | A :: term(), 88 | B :: term(). 89 | 90 | zipfold(F, Acc, [], []) when is_function(F) -> Acc; 91 | zipfold(F, Acc, [A | As], [B | Bs]) -> 92 | zipfold(F, F(Acc, A, B), As, Bs). 93 | 94 | %% 95 | %% @doc Zips the elements of the given lists 96 | %% left folding them with the accumulator. 97 | %% 98 | -spec zipfold(F, Acc, [A], [B], [C]) -> Acc when 99 | F :: fun((Acc, A, B, C) -> Acc), 100 | Acc :: term(), 101 | A :: term(), 102 | B :: term(), 103 | C :: term(). 104 | 105 | zipfold(F, Acc, [], [], []) when is_function(F) -> Acc; 106 | zipfold(F, Acc, [A | As], [B | Bs], [C | Cs]) -> 107 | zipfold(F, F(Acc, A, B, C), As, Bs, Cs). 108 | 109 | 110 | %% 111 | %% @doc The smallest free number. 112 | %% 113 | %% Computes the smallest natural number not in a given finite list 114 | %% of unique natural numbers. 115 | %% 116 | %% This algorithm takes linear time and space (tail-optimized). 117 | %% In comparison, the straightforward algorithm is quadratic: 118 | %% ``` 119 | %% hd(lists:seq(0, N) -- List)''' 120 | %% 121 | %% See [B1] Chapter 1, pp. 1–6. 122 | %% 123 | -spec minfree([non_neg_integer()]) -> non_neg_integer(). 124 | 125 | minfree(List) -> minfrom(0, {length(List), List}). 126 | 127 | minfrom(A, {0, []}) -> A; 128 | minfrom(A, {N, List}) -> 129 | B = A + 1 + N div 2, 130 | {Us, Vs} = lists:partition(fun(X) -> X < B end, List), % Θ(N) 131 | M = length(Us), 132 | case B - A of 133 | M -> minfrom(B, {N - M, Vs}); 134 | _ -> minfrom(A, {M, Us}) 135 | end. 136 | 137 | 138 | %% 139 | %% @doc Maximum surpasser count. 140 | %% 141 | %% `x[j]' is a surpasser of `x[i]' if `i < j' and `x[i] < x[j]'. 142 | %% The surpasser count of an element is the number of 143 | %% its surpassers. 144 | %% 145 | %% The complexity of the divide and conquer version of the MSC algorithm is `O(n log n)'. 146 | %% 147 | %% See [B1] Chapter 2, pp. 7–11. 148 | %% 149 | %% @see msc2/1 150 | %% 151 | -spec msc(list()) -> non_neg_integer(). 152 | 153 | msc(List) -> max([C || {_, C} <- table(List)]). 154 | 155 | 156 | -spec table([A]) -> [{A, Count :: non_neg_integer()}]. 157 | 158 | table([X]) -> [{X, 0}]; 159 | table(List) -> 160 | M = length(List), 161 | N = M div 2, 162 | {Ys, Zs} = lists:split(N, List), 163 | join(M - N, table(Ys), table(Zs)). 164 | 165 | -spec join(non_neg_integer(), [A], [A]) -> [A]. 166 | 167 | join(0, Txs, []) -> Txs; 168 | join(_N, [], Tys) -> Tys; 169 | join(N, [{X, C} | Txst] = Txs, [{Y, D} | Tyst] = Tys) -> 170 | case X < Y of 171 | true -> [{X, C + N} | join(N, Txst, Tys)]; 172 | false -> [{Y, D} | join(N - 1, Txs, Tyst)] 173 | end. 174 | 175 | 176 | %% 177 | %% @doc MSC algorithm running in quadratic time. 178 | %% 179 | -spec msc2(list()) -> non_neg_integer(). 180 | 181 | msc2(List) -> max([scount(X, Xs) || [X | Xs] <- tails(List)]). 182 | 183 | scount(X, Xs) -> length(filter(fun(Y) -> X < Y end, Xs)). 184 | 185 | tails([]) -> []; 186 | tails([_ | Xs] = List) -> [List | tails(Xs)]. 187 | 188 | 189 | %% ============================================================================= 190 | %% Unit tests 191 | %% ============================================================================= 192 | 193 | -ifdef(TEST). 194 | -include_lib("eunit/include/eunit.hrl"). 195 | 196 | cross_test_() -> 197 | F = fun(X) -> 2 * X end, 198 | G = fun(Y) -> 3 * Y end, 199 | [ 200 | ?_assertEqual({2, 6}, cross({F, G}, {1, 2})) 201 | ]. 202 | 203 | zipfold_test_() -> [ 204 | ?_assertEqual(2 + 6 + 12, 205 | zipfold(fun(Acc, A, B) -> Acc + A * B end, 0, [1, 2, 3], [2, 3, 4])), 206 | ?_assertEqual(6 + 24 + 60, 207 | zipfold(fun(Acc, A, B, C) -> Acc + A * B * C end, 0, [1, 2, 3], [2, 3, 4], [3, 4, 5]))]. 208 | 209 | frequencies_test() -> 210 | ?assertEqual(#{1 => 2, 2 => 2, 3 => 3, 4 => 1}, 211 | frequencies([1, 2, 3, 2, 3, 4, 1, 3])). 212 | 213 | group_by_test() -> 214 | ?assertEqual(#{1 => ["a"], 2 => ["as", "aa"], 3 => ["asd"], 4 => ["asdf", "qwer"]}, 215 | group_by(fun erlang:length/1, ["a", "as", "asd", "aa", "asdf", "qwer"])). 216 | 217 | min_by_test_() -> 218 | F = fun(X) -> X * X end, 219 | [ 220 | ?_assertEqual(1, min_by(F, [-3, 1, 5])) 221 | ]. 222 | 223 | minfree_test_() -> 224 | List = [4, 0, 5, 7, 3, 10, 2, 1], 225 | [ 226 | ?_assertEqual(0, minfree(lists:seq(1, 10))), 227 | ?_assertEqual(0, minfree(lists:reverse(lists:seq(1, 10)))), 228 | ?_assertEqual(9, minfree(lists:seq(0, 8))), 229 | ?_assertEqual(9, minfree(lists:reverse(lists:seq(0, 8)))), 230 | ?_assertEqual(6, minfree(List)), 231 | ?_assertEqual(hd(lists:seq(0, 8) -- List), minfree(List)) 232 | ]. 233 | 234 | scount([X | Xs]) -> scount(X, Xs). 235 | 236 | msc_test_() -> 237 | Word = "GENERATING", 238 | [ 239 | ?_assertEqual(5, scount(hd(Word), tl(Word))), 240 | ?_assertEqual([5, 6, 2, 5, 1, 4, 0, 1, 0, 0], map(fun scount/1, tails(Word))), 241 | ?_assertEqual(6, msc2(Word)), 242 | ?_assertEqual(6, msc(Word)), 243 | ?_assertEqual([6, 6, 5, 5, 4, 4, 1, 1, 0, 0], map(fun msc/1, tails(Word))) 244 | ]. 245 | 246 | tails_test_() -> [ 247 | ?_assertEqual([[1, 2, 3], [2, 3], [3]], tails([1, 2, 3])) 248 | ]. 249 | 250 | -endif. 251 | -------------------------------------------------------------------------------- /lib/ndpar/src/lazy.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc One of the implementations of 3 | %% [https://groups.google.com/g/erlang-programming/c/ZUHZpH0wsOA 4 | %% coinductive data types]. 5 | %% 6 | %% @see lazy2 7 | %% 8 | -module(lazy). 9 | -author("Andrey Paramonov "). 10 | 11 | -export([gen/2, filter/2, foldl/3, map/2, take/2]). 12 | -export([natural_numbers/0]). 13 | 14 | -dialyzer(no_improper_lists). 15 | 16 | -type lazy_seq() :: fun(() -> [term() | lazy_seq()]). 17 | -type integers() :: fun(() -> [pos_integer() | integers()]). 18 | 19 | %% 20 | %% @doc Generates lazy (infinite) sequence of elements `E' 21 | %% using generating function `F'. 22 | %% 23 | -spec gen(E0, F) -> lazy_seq() when 24 | F :: fun((ECurrent) -> ENext), 25 | E0 :: term(), 26 | ECurrent :: term(), 27 | ENext :: term(). 28 | 29 | gen(E, F) -> fun() -> [E | gen(F(E), F)] end. 30 | 31 | %% 32 | %% @doc Generates sequence of integers. 33 | %% 34 | -spec integers_from(pos_integer()) -> integers(). 35 | integers_from(K) -> gen(K, fun(N) -> N + 1 end). 36 | 37 | %% 38 | %% @doc Generates sequence of natural numbers. 39 | %% 40 | -spec natural_numbers() -> integers(). 41 | natural_numbers() -> integers_from(1). 42 | 43 | %% 44 | %% @doc Filters the lazy sequence `CE' using predicate `P'. 45 | %% 46 | -spec filter(P :: Predicate, CE :: lazy_seq()) -> fun(() -> lazy_seq()) when 47 | Predicate :: fun((term()) -> boolean()). 48 | 49 | filter(P, CE) -> 50 | fun() -> 51 | case CE() of 52 | [] -> []; 53 | [X | Xs] -> 54 | case P(X) of 55 | true -> [X | filter(P, Xs)]; 56 | false -> (filter(P, Xs))() 57 | end 58 | end 59 | end. 60 | 61 | %% 62 | %% @doc Left folds the lazy sequence `CE' using function `F' and accumulator `A'. 63 | %% 64 | -spec foldl(F, Acc0 :: Acc, lazy_seq()) -> Acc1 :: Acc when 65 | F :: fun((term(), AccIn :: Acc) -> AccOut :: Acc), 66 | Acc :: term(). 67 | 68 | foldl(F, A, CE) -> 69 | case CE() of 70 | [] -> A; 71 | [X | Xs] -> foldl(F, F(A, X), Xs) 72 | end. 73 | 74 | %% 75 | %% @doc Maps the lazy sequence `CE' using function `F'. 76 | %% 77 | -spec map(F, lazy_seq()) -> lazy_seq() when 78 | F :: fun((term()) -> term()). 79 | 80 | map(F, CE) -> 81 | fun() -> 82 | case CE() of 83 | [] -> []; 84 | [X | Xs] -> [F(X) | map(F, Xs)] 85 | end 86 | end. 87 | 88 | %% 89 | %% @doc Returns first `N' elements of the given lazy sequence. 90 | %% 91 | -spec take(non_neg_integer(), lazy_seq()) -> [term()]. 92 | 93 | take(N, LazySeq) -> take([], N, LazySeq). 94 | 95 | take(A, 0, _) -> lists:reverse(A); 96 | take(A, N, CE) -> 97 | [X | Xs] = CE(), 98 | take([X | A], N - 1, Xs). 99 | 100 | %% ============================================================================= 101 | %% Unit tests 102 | %% ============================================================================= 103 | 104 | -ifdef(TEST). 105 | -include_lib("eunit/include/eunit.hrl"). 106 | 107 | filter_first(N, P, CE) -> take(N, filter(P, CE)). 108 | 109 | foldl_first(0, _, A, _) -> A; 110 | foldl_first(N, F, A, CE) -> 111 | case CE() of 112 | [] -> A; 113 | [X | Xs] -> foldl_first(N - 1, F, F(A, X), Xs) 114 | end. 115 | 116 | map_first(N, F, CE) -> take(N, map(F, CE)). 117 | 118 | first_natural_numbers(N) -> take(N, natural_numbers()). 119 | 120 | first_even_numbers(N) -> filter_first(N, fun(X) -> X rem 2 =:= 0 end, natural_numbers()). 121 | 122 | first_squares(N) -> map_first(N, fun(X) -> X * X end, natural_numbers()). 123 | 124 | first_sum(N) -> foldl_first(N, fun(X, Sum) -> X + Sum end, 0, natural_numbers()). 125 | 126 | filter_test() -> 127 | F = filter(fun(X) -> 10 < X end, natural_numbers()), 128 | [X | _] = F(), 129 | ?assertEqual(11, X). 130 | 131 | map_test() -> 132 | [X | _] = (map(fun(X) -> X * 2 end, natural_numbers()))(), 133 | ?assertEqual(2, X). 134 | 135 | first_natural_numbers_test() -> 136 | ?assertEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], first_natural_numbers(10)). 137 | 138 | first_even_numbers_test_() -> [ 139 | ?_assertEqual([2, 4, 6, 8, 10, 12, 14, 16, 18, 20], first_even_numbers(10)), 140 | ?_assertEqual([2, 4, 6, 8, 10, 12, 14, 16, 18, 20], take(10, gen(2, fun(N) -> N + 2 end))) 141 | ]. 142 | 143 | first_squares_test() -> 144 | ?assertEqual([1, 4, 9, 16, 25, 36, 49, 64, 81, 100], first_squares(10)). 145 | 146 | powers_of_two_test() -> 147 | ?assertEqual([1, 2, 4, 8, 16, 32, 64, 128, 256, 512], take(10, gen(1, fun(N) -> 2 * N end))). 148 | 149 | first_sum_test() -> 150 | ?assertEqual(55, first_sum(10)). 151 | 152 | -endif. 153 | -------------------------------------------------------------------------------- /lib/ndpar/src/lazy2.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Another implementations of 3 | %% [https://groups.google.com/g/erlang-programming/c/ZUHZpH0wsOA 4 | %% coinductive data types]. 5 | %% 6 | %% @see lazy 7 | %% 8 | -module(lazy2). 9 | -author("Andrey Paramonov "). 10 | 11 | -export([gen/2, filter/2, foldl/3, map/2, take/2]). 12 | -export([natural_numbers/0]). 13 | 14 | -dialyzer(no_improper_lists). 15 | 16 | -type lazy_seq() :: [term() | fun(() -> lazy_seq())]. 17 | -type integers() :: [pos_integer() | fun(() -> integers())]. 18 | 19 | %% 20 | %% @doc Generates lazy (infinite) sequence of elements `E' 21 | %% using generating function `F'. 22 | %% 23 | gen(E, F) -> [E | fun() -> gen(F(E), F) end]. 24 | 25 | %% 26 | %% @doc Generates sequence of integers. 27 | %% 28 | integers_from(K) -> gen(K, fun(N) -> N + 1 end). 29 | 30 | %% 31 | %% @doc Generates sequence of natural numbers. 32 | %% 33 | natural_numbers() -> integers_from(1). 34 | 35 | %% 36 | %% @doc Filters the given lazy sequence using predicate `Pred'. 37 | %% 38 | filter(_, []) -> []; 39 | filter(Pred, [X | Gen]) -> 40 | case Pred(X) of 41 | true -> [X | fun() -> filter(Pred, Gen()) end]; 42 | false -> filter(Pred, Gen()) 43 | end. 44 | 45 | %% 46 | %% @doc Left folds the given lazy sequence using function `Fun' and accumulator `Acc'. 47 | %% 48 | foldl(_, Acc, []) -> {Acc, []}; 49 | foldl(Fun, Acc, [X | Gen]) -> {Fun(X, Acc), Gen()}. 50 | 51 | %% 52 | %% @doc Maps the given lazy sequence using function `Fun'. 53 | %% 54 | map(_, []) -> []; 55 | map(Fun, [X | Gen]) -> [Fun(X) | fun() -> map(Fun, Gen()) end]. 56 | 57 | %% 58 | %% @doc Returns first `N' elements of the given lazy sequence. 59 | %% 60 | take(N, LazySeq) -> take([], N, LazySeq). 61 | 62 | take(Acc, 0, _) -> Acc; 63 | take(Acc, N, [X | Gen]) -> take(Acc ++ [X], N - 1, Gen()). 64 | 65 | %% ============================================================================= 66 | %% Unit tests 67 | %% ============================================================================= 68 | 69 | -ifdef(TEST). 70 | -include_lib("eunit/include/eunit.hrl"). 71 | 72 | filter_first(N, Pred, LazyList) -> take(N, filter(Pred, LazyList)). 73 | 74 | foldl_first(0, _, Acc, _) -> Acc; 75 | foldl_first(N, Fun, Acc, LazyList) -> 76 | {NewAcc, LazyTail} = foldl(Fun, Acc, LazyList), 77 | foldl_first(N - 1, Fun, NewAcc, LazyTail). 78 | 79 | map_first(N, Fun, LazyList) -> take(N, map(Fun, LazyList)). 80 | 81 | first_natural_numbers(N) -> take(N, natural_numbers()). 82 | 83 | first_even_numbers(N) -> filter_first(N, fun(X) -> X rem 2 == 0 end, natural_numbers()). 84 | 85 | first_squares(N) -> map_first(N, fun(X) -> X * X end, natural_numbers()). 86 | 87 | first_sum(N) -> foldl_first(N, fun(X, Sum) -> X + Sum end, 0, natural_numbers()). 88 | 89 | filter_test() -> 90 | [X | _] = filter(fun(X) -> 10 < X end, natural_numbers()), 91 | ?assertEqual(11, X). 92 | 93 | foldl_test() -> 94 | {P, _} = foldl(fun(X, Prod) -> X * Prod end, 1, natural_numbers()), 95 | ?assertEqual(1, P). 96 | 97 | map_test() -> 98 | [X | _] = map(fun(X) -> X * 2 end, natural_numbers()), 99 | ?assertEqual(2, X). 100 | 101 | first_natural_numbers_test() -> 102 | ?assertEqual([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], first_natural_numbers(10)). 103 | 104 | first_even_numbers_test() -> 105 | ?assertEqual([2, 4, 6, 8, 10, 12, 14, 16, 18, 20], first_even_numbers(10)). 106 | 107 | first_squares_test() -> 108 | ?assertEqual([1, 4, 9, 16, 25, 36, 49, 64, 81, 100], first_squares(10)). 109 | 110 | first_sum_test() -> 111 | ?assertEqual(55, first_sum(10)). 112 | 113 | -endif. 114 | -------------------------------------------------------------------------------- /lib/ndpar/src/marking.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Maximum marking problem. 3 | %% 4 | %% Algorithms for solving maximum marking problem in which 5 | %% the marking criterion can be formulated as a regular expression. 6 | %% 7 | %% @reference [B1] Chapter 11, pp. 73–78 8 | %% 9 | -module(marking). 10 | -author("Andrey Paramonov "). 11 | 12 | -import(lists, [foldl/3, split/2]). 13 | 14 | -export([mnss/1]). 15 | 16 | %% 17 | %% @doc Computes maximum non-segment sum in linear time. 18 | %% 19 | %% A segment of a list is a contiguous subsequence, 20 | %% while a non-segment is a subsequence that is not a segment. 21 | %% There are no non-segments of a list with two or fewer elements. 22 | %% 23 | %% For example, 24 | %% ``` 25 | %% [-4, -3, -7, 2, 1, -2, -1, -4] 26 | %% ''' 27 | %% has maximum segment sum 3 (from the segment [2, 1]) and 28 | %% maximum non-segment sum 2 (from the non-segment [2, 1, -1]). 29 | %% 30 | -spec mnss([integer()]) -> integer(). 31 | 32 | mnss(Ints) -> 33 | {L, R} = split(3, Ints), 34 | {_, _, _, Sum} = foldl(fun h/2, start(L), R), 35 | Sum. 36 | 37 | start([X, Y, Z]) -> 38 | {0, lists:max([X + Y + Z, Y + Z, Z]), lists:max([X, X + Y, Y]), X + Z}. 39 | 40 | h(X, {E, S, M, N}) -> 41 | {E, max(S, E) + X, max(M, S), max(N, max(N, M) + X)}. 42 | 43 | %% ============================================================================= 44 | %% Unit tests 45 | %% ============================================================================= 46 | 47 | -ifdef(TEST). 48 | -include_lib("eunit/include/eunit.hrl"). 49 | 50 | mnss_test_() -> 51 | [ 52 | ?_assertEqual(2, mnss([-4, -3, -7, 2, 1, -2, -1, -4])) 53 | ]. 54 | 55 | -endif. 56 | -------------------------------------------------------------------------------- /lib/ndpar/src/mintrees.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Problem: given a sequence `Xs = [X1, X2,..., Xn]' of natural numbers, 3 | %% find a linear-time algorithm to build a tree with fringe `Xs' that 4 | %% minimises {@link cost/1. cost} function. 5 | %% 6 | %% The fringe of a tree is the list of labels at the leaves in left-to-right order. 7 | %% 8 | %% The presented {@link mincost_tree/1. mincost_tree} algorithm can be used 9 | %% to solve the following problem: 10 | %% 11 | %% Given an arbitrary list of trees `Ts = [T1, T2,..., Tn]' together with their 12 | %% heights `Hs = [H1, H2,..., Hn]', find a linear time algorithm to combine 13 | %% them into a single tree `T' of minimum height. 14 | %% Trees `Ts' should appear as subtrees of the tree `T' in the same order 15 | %% as they appear in the list `Ts'. 16 | %% 17 | %% The solution is: run {@link mincost_tree/1. mincost_tree} algorithm for 18 | %% fringe `Hs'. In the resulting tree replace all leaves `Hi' with the 19 | %% corresponding tree `Ti'. 20 | %% 21 | %% @reference [B1] Chapter 7, pp. 41–49 22 | %% 23 | -module(mintrees). 24 | -author("Andrey Paramonov "). 25 | 26 | -export([cost/1]). 27 | -export([mincost_tree/1, trees/1]). 28 | -export([mincost_tree2/1, trees2/1]). 29 | 30 | -import(core, [foldl1/2]). 31 | -import(lists, [concat/1, foldl/3, map/2]). 32 | 33 | %% ============================================================================= 34 | %% Types 35 | %% ============================================================================= 36 | 37 | -type tree(Type) :: {leaf, Type} | {fork, tree(Type), tree(Type)}. 38 | -type forest(Type) :: [tree(Type)]. 39 | 40 | %% 41 | %% @doc Defines cost of the integer tree. 42 | %% 43 | -spec cost(Tree :: tree(integer())) -> integer(). 44 | 45 | cost({leaf, X}) -> X; 46 | cost({fork, U, V}) -> 1 + max(cost(U), cost(V)). 47 | 48 | 49 | %% 50 | %% @doc General right fold on nonempty lists. 51 | %% 52 | -spec foldrn(F, G, [A]) -> B when 53 | F :: fun((A, B) -> B), 54 | G :: fun((A) -> B), 55 | A :: term(), 56 | B :: term(). 57 | 58 | foldrn(_, G, [X]) -> G(X); 59 | foldrn(F, G, [X | Xs]) -> F(X, foldrn(F, G, Xs)). 60 | 61 | %% ============================================================================= 62 | %% Exponential algorithms 63 | %% ============================================================================= 64 | 65 | %% 66 | %% @doc Returns a minimal cost tree for the given fringe. 67 | %% @see trees2/1 68 | %% 69 | -spec mincost_tree2(Fringe :: [integer()]) -> tree(integer()). 70 | 71 | mincost_tree2(Fringe) -> core:min_by(fun cost/1, trees2(Fringe)). 72 | 73 | 74 | %% 75 | %% @doc Returns list of trees for the given fringe. 76 | %% Intuitive algorithm to build trees by prefixing 77 | %% previously built right sub-trees. 78 | %% 79 | -spec trees2(Fringe :: [integer()]) -> [tree(integer())]. 80 | 81 | trees2(Fringe) -> 82 | F = fun(P, Ts) -> concat(map(fun(T) -> prefixes2(P, T) end, Ts)) end, 83 | G = fun(X) -> [{leaf, X}] end, 84 | foldrn(F, G, Fringe). 85 | 86 | prefixes2(X, {leaf, _} = T) -> [{fork, {leaf, X}, T}]; 87 | prefixes2(X, {fork, U, V} = T) -> 88 | Us = [{fork, Un, V} || Un <- prefixes2(X, U)], 89 | [{fork, {leaf, X}, T} | Us]. 90 | 91 | 92 | %% 93 | %% @doc Returns list of trees for the given fringe. 94 | %% Another algorithm to build trees by rolling up 95 | %% left spines 96 | %% 97 | -spec trees(Fringe :: [integer()]) -> [tree(integer())]. 98 | 99 | trees(Fringe) -> map(fun rollup/1, forests(Fringe)). 100 | 101 | 102 | -spec forests(Fringe :: [integer()]) -> [forest(integer())]. 103 | 104 | forests(Fringe) -> 105 | F = fun(P, Ts) -> concat(map(fun(T) -> prefixes(P, T) end, Ts)) end, 106 | G = fun(X) -> [[{leaf, X}]] end, 107 | foldrn(F, G, Fringe). 108 | 109 | 110 | -spec prefixes(X :: integer(), Ts :: forest(integer())) -> [forest(integer())]. 111 | 112 | prefixes(X, Ts) -> 113 | [[{leaf, X} | split_and_rollup(K, Ts)] || K <- lists:seq(1, length(Ts))]. 114 | 115 | split_and_rollup(K, Ts) -> 116 | {L, R} = lists:split(K, Ts), 117 | [rollup(L) | R]. 118 | 119 | %% 120 | %% @doc Left fold forest (lists of trees) into a single tree. 121 | %% 122 | -spec rollup(Forest :: forest(T)) -> tree(T). 123 | 124 | rollup(Forest) -> 125 | F = fun(L, R) -> {fork, L, R} end, 126 | foldl1(F, Forest). 127 | 128 | %% ============================================================================= 129 | %% Linear algorithm 130 | %% ============================================================================= 131 | 132 | %% 133 | %% @doc Returns a minimal cost tree for the given fringe. 134 | %% 135 | -spec mincost_tree(Fringe :: [integer()]) -> tree(integer()). 136 | 137 | mincost_tree(Fringe) -> 138 | G = fun(X) -> [leaf(X)] end, 139 | TCs = foldrn(fun insert/2, G, Fringe), 140 | F = fun(L, R) -> {fork, L, R} end, 141 | foldl1(F, [T || {_, T} <- TCs]). 142 | 143 | insert(X, Ts) -> [leaf(X) | split(X, Ts)]. 144 | 145 | split(_, [U]) -> [U]; 146 | split(X, [{Cu, _} = U, {Cv, _} = V | Ts] = Spine) -> 147 | Cxu = max(X, Cu), 148 | if 149 | Cxu < Cv -> Spine; 150 | true -> split(X, [fork(U, V) | Ts]) 151 | end. 152 | 153 | %% Smart constructors to pair tree node with its cost. 154 | 155 | leaf(X) -> {X, {leaf, X}}. 156 | fork({A, U}, {B, V}) -> {1 + max(A, B), {fork, U, V}}. 157 | 158 | %% ============================================================================= 159 | %% Unit tests 160 | %% ============================================================================= 161 | 162 | -ifdef(TEST). 163 | -include_lib("eunit/include/eunit.hrl"). 164 | 165 | trees_test_() -> 166 | [ 167 | ?_assertEqual(14, length(trees([1, 2, 3, 4, 5]))) 168 | ]. 169 | 170 | foldrn_test_() -> 171 | F = fun(X, Y) -> X + Y end, 172 | G = fun(X) -> X + 1 end, 173 | [ 174 | ?_assertEqual(16, foldrn(F, G, [1, 2, 3, 4, 5])) 175 | ]. 176 | 177 | trees2_test_() -> 178 | [ 179 | ?_assertEqual(14, length(trees2([1, 2, 3, 4, 5]))) 180 | ]. 181 | 182 | mincost_tree_test_() -> 183 | [ 184 | ?_assertEqual( 185 | {fork, 186 | {fork, 187 | {fork, 188 | {fork, 189 | {leaf, 1}, 190 | {leaf, 2} 191 | }, 192 | {leaf, 3} 193 | }, 194 | {leaf, 4} 195 | }, 196 | {leaf, 5} 197 | }, 198 | mincost_tree2([1, 2, 3, 4, 5]) 199 | ), 200 | ?_assertEqual( 201 | mincost_tree2([1, 2, 3, 4, 5]), 202 | mincost_tree([1, 2, 3, 4, 5]) 203 | ) 204 | ]. 205 | 206 | -endif. 207 | -------------------------------------------------------------------------------- /lib/ndpar/src/modules.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Various functions to work with modules. 3 | %% 4 | %% @reference [A2] Chapter 8 exercises 5 | %% 6 | -module(modules). 7 | -export([all_loaded/0, is_loaded/1, exported/1]). 8 | 9 | %% 10 | %% @doc Returns a list of all modules that have been loaded in ERTS. 11 | %% 12 | -spec all_loaded() -> [module()]. 13 | all_loaded() -> [M || {M, _} <- code:all_loaded()]. 14 | 15 | %% 16 | %% @doc Returns `true' if the Module has been loaded. 17 | %% 18 | -spec is_loaded(module()) -> boolean(). 19 | is_loaded(Module) -> lists:member(Module, all_loaded()). 20 | 21 | %% 22 | %% @doc Returns a list of functions exported by the Module. 23 | %% 24 | -spec exported(module()) -> [{atom(), non_neg_integer()}]. 25 | exported(Module) -> Module:module_info(exports). 26 | 27 | 28 | -include_lib("eunit/include/eunit.hrl"). 29 | 30 | is_loaded_test() -> 31 | ?assert(is_loaded(?MODULE)). 32 | 33 | exported_test() -> 34 | ?assert(lists:member({test, 0}, exported(?MODULE))). 35 | 36 | % The following assertions have been tested in Erlang 23.1.5 37 | 38 | % Exercise 1, p.138 39 | dict_test() -> 40 | ?assertEqual(23, length(exported(dict))). 41 | 42 | % Exercise 2, p.139 43 | module_with_most_functions_test() -> 44 | ?assertMatch({erlang, _}, % 339 45 | lists:last(lists:keysort(2, [{M, length(exported(M))} || M <- all_loaded()]))). 46 | 47 | % Exercise 2, p.139 48 | most_common_function_name_test() -> 49 | AllFunctions = [F || M <- all_loaded(), {F, _} <- exported(M)], 50 | Functions = lists:keysort(2, maps:to_list(core:frequencies(AllFunctions))), 51 | ?assertMatch({module_info, _}, lists:last(Functions)). % 284 52 | 53 | % Exercise 2, p.139 54 | all_unambiguous_functions_test() -> 55 | AllFunctions = lists:usort([{F, M} || M <- all_loaded(), {F, _} <- exported(M)]), 56 | GroupFunctions = core:group_by(fun({F, _}) -> F end, AllFunctions), 57 | SingleModuleFuns = lists:filter(fun({_, L}) -> length(L) =:= 1 end, maps:to_list(GroupFunctions)), 58 | Result = lists:flatmap(fun({_, L}) -> L end, SingleModuleFuns), 59 | %io:format("~p~n", [Result]), % 1447 functions 60 | ?assert(lists:member({'*', erlang}, Result)). 61 | -------------------------------------------------------------------------------- /lib/ndpar/src/ndpar_lib.app.src: -------------------------------------------------------------------------------- 1 | {application, ndpar_lib, 2 | [{description, "My Erlang library"}, 3 | {vsn, "0.2.0"}, 4 | {registered, []}, 5 | {applications, 6 | [kernel, 7 | stdlib 8 | ]}, 9 | {env, []}, 10 | {modules, []}, 11 | 12 | {maintainers, ["Andrey Paramonov"]}, 13 | {licenses, ["Apache 2.0"]}, 14 | {links, [{"GitHub", "https://github.com/ndpar/erlang/tree/master/lib/ndpar"}]} 15 | ]}. 16 | -------------------------------------------------------------------------------- /lib/ndpar/src/pkix.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Utility functions to work with PKIX standard. 3 | %% 4 | -module(pkix). 5 | -author("Andrey Paramonov"). 6 | 7 | -export([public_key/2]). 8 | 9 | %% 10 | %% @doc Converts RSA public key from PKCS#1 format to PKIX. 11 | %% 12 | -spec public_key(FileNameIn :: string(), FileNameOut :: string()) -> ok. 13 | 14 | public_key(FileIn, FileOut) when is_list(FileIn), is_list(FileOut) -> 15 | {ok, PemInBin} = file:read_file(FileIn), 16 | PemOutBin = public_key(PemInBin), 17 | ok = file:write_file(FileOut, PemOutBin). 18 | 19 | public_key(PemBin) when is_binary(PemBin) -> 20 | [SinglePemEntryIn] = public_key:pem_decode(PemBin), 21 | PemEntryIn = public_key:pem_entry_decode(SinglePemEntryIn), 22 | PemEntryOut = public_key:pem_entry_encode('SubjectPublicKeyInfo', PemEntryIn), 23 | public_key:pem_encode([PemEntryOut]). 24 | 25 | %% ============================================================================= 26 | %% Unit tests 27 | %% ============================================================================= 28 | 29 | -ifdef(TEST). 30 | -include_lib("eunit/include/eunit.hrl"). 31 | 32 | -define(PK_PKCS1, 33 | <<"-----BEGIN RSA PUBLIC KEY----- 34 | MIIBCgKCAQEB1Ycpxf/heFNg16K1MuqmMpybi9la8QVCK0A2hjD2NuHLC4R9dHmL 35 | 2VJJtCpHlxu1kD/Iepe31UGsWZlhuLDrbPJPffx5hDTq4uPU50HU/snGliCAFSBa 36 | UCWLaIpUdXUbNhxXyuvibbPPkqpPSmndk24Up6WAcs/0YQNe2dRI0RYeLA3/3yo2 37 | tCyrxrLY/ay3vEwROJDmkSTk+9SIr5+8VVDHWGxUEuAabenyqJZufBg8Wkyt2T6F 38 | /msShiEd7mLyknQ1j1aeIPFp8aLQEllxBQOsatHaj8LGxMaTPHjRrjfeMKCoRnb9 39 | EdldQ7W5PAMvUs7CseY2/JTp/qLzIaZo/wIDAQAB 40 | -----END RSA PUBLIC KEY-----">>). 41 | 42 | -define(PK_PKIX, 43 | <<"-----BEGIN PUBLIC KEY----- 44 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEB1Ycpxf/heFNg16K1Muqm 45 | Mpybi9la8QVCK0A2hjD2NuHLC4R9dHmL2VJJtCpHlxu1kD/Iepe31UGsWZlhuLDr 46 | bPJPffx5hDTq4uPU50HU/snGliCAFSBaUCWLaIpUdXUbNhxXyuvibbPPkqpPSmnd 47 | k24Up6WAcs/0YQNe2dRI0RYeLA3/3yo2tCyrxrLY/ay3vEwROJDmkSTk+9SIr5+8 48 | VVDHWGxUEuAabenyqJZufBg8Wkyt2T6F/msShiEd7mLyknQ1j1aeIPFp8aLQEllx 49 | BQOsatHaj8LGxMaTPHjRrjfeMKCoRnb9EdldQ7W5PAMvUs7CseY2/JTp/qLzIaZo 50 | /wIDAQAB 51 | -----END PUBLIC KEY----- 52 | 53 | ">>). 54 | 55 | public_key_test() -> 56 | ?assertEqual(?PK_PKIX, public_key(?PK_PKCS1)). 57 | 58 | -endif. 59 | -------------------------------------------------------------------------------- /lib/ndpar/src/primes.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Various functions to work with prime numbers. 3 | %% 4 | -module(primes). 5 | -author("Andrey Paramonov"). 6 | 7 | -export([is_prime/1, primes_upto/1, random_prime/2]). 8 | -export([pollard_p1/2]). 9 | 10 | %% 11 | %% @doc Returns `true' if the given `N' is a prime number. 12 | %% 13 | -spec is_prime(N :: pos_integer()) -> boolean(). 14 | 15 | is_prime(N) -> miller_rabin(N). 16 | 17 | miller_rabin(2) -> true; 18 | miller_rabin(N) when N rem 2 =:= 0 -> false; 19 | miller_rabin(N) when 3 =< N, N rem 2 =:= 1 -> 20 | {S, T} = maths:factor2(N - 1), 21 | miller_rabin(N, S, T, 0). 22 | 23 | %% 24 | %% Keep track of the probability of a false result in `K'. 25 | %% The probability is at most 2^-K. 26 | %% Loop until the probability of a false result is small enough. 27 | %% 28 | miller_rabin(N, S, T, K) when K < 128 -> 29 | A = rnd:random(2, N - 1), 30 | case maths:mod_exp(A, S, N) of 31 | 1 -> miller_rabin(N, S, T, K + 2); 32 | V -> case mr_squaring(0, V, N, T) of 33 | composite -> false; 34 | candidate -> miller_rabin(N, S, T, K + 2) 35 | end 36 | end; 37 | miller_rabin(_, _, _, _) -> true. 38 | 39 | %% 40 | %% The sequence v, v^2,..., v^2^t must finish on the value 1, 41 | %% and the last value not equal to 1 must be n-1 if n is a prime. 42 | %% 43 | mr_squaring(_I, V, N, _T) when V =:= N - 1 -> candidate; 44 | mr_squaring(I, _V, _N, T) when I =:= T - 1 -> composite; 45 | mr_squaring(I, V, N, T) -> mr_squaring(I + 1, maths:mod_exp(V, 2, N), N, T). 46 | 47 | 48 | %% 49 | %% @doc Find all prime numbers up to specified value. 50 | %% Works relatively fast for `N < 5,000,000'. 51 | %% 52 | -spec primes_upto(N :: 2..5000000) -> [integer()]. 53 | 54 | primes_upto(N) when 2 =< N, N =< 5000000 -> eratosthenes(math:sqrt(N), lists:seq(2, N)). 55 | 56 | %% 57 | %% Recursion implementation of Eratosthenes sieve algorithm 58 | %% Author: Zac Brown 59 | %% 60 | %% See also: https://github.com/ndpar/algorithms/blob/master/mymath.erl 61 | %% 62 | eratosthenes(Max, [H | T]) when H =< Max -> [H | eratosthenes(Max, sieve([H | T], H))]; 63 | eratosthenes(_Max, L) -> L. 64 | 65 | sieve([H | T], N) when H rem N =/= 0 -> [H | sieve(T, N)]; 66 | sieve([_ | T], N) -> sieve(T, N); 67 | sieve([], _N) -> []. 68 | 69 | 70 | %% 71 | %% @doc Returns a random prime in the interval `[L, U]'. 72 | %% 73 | -spec random_prime(L :: pos_integer(), U :: pos_integer()) -> pos_integer(). 74 | 75 | random_prime(L, U) when 2 < L, L =< U -> 76 | random_prime(L, U, 100 * (maths:ilog2(U) + 1) - 1). 77 | 78 | random_prime(L, U, R) when 0 < R -> 79 | N = rnd:random(L, U), 80 | case is_prime(N) of 81 | true -> N; 82 | false -> random_prime(L, U, R - 1) 83 | end. 84 | 85 | 86 | %% 87 | %% @doc Pollard’s `p − 1' algorithm for factoring integers. 88 | %% 89 | %% See [MvOV1] Chapter 3.2.3. Algorithm 3.14 90 | %% 91 | -spec pollard_p1(pos_integer(), pos_integer()) -> pos_integer() | error. 92 | 93 | pollard_p1(N, B) -> 94 | pollard_p1(N, B, rnd:random(2, N - 1)). 95 | 96 | pollard_p1(N, B, A) -> 97 | case maths:gcd(A, N) of 98 | 1 -> pollard_p1_(N, A, primes_upto(B)); 99 | D -> D 100 | end. 101 | 102 | pollard_p1_(_, _, []) -> error; 103 | pollard_p1_(N, A, [Q | T]) -> 104 | L = erlang:trunc(maths:log(Q, N)), 105 | A1 = maths:mod_exp(A, maths:pow(Q, L), N), 106 | case maths:gcd(A1 - 1, N) of 107 | 1 -> pollard_p1_(N, A1, T); 108 | N -> pollard_p1_(N, A1, T); 109 | D -> D 110 | end. 111 | 112 | %% ============================================================================= 113 | %% Unit tests 114 | %% ============================================================================= 115 | 116 | -include_lib("eunit/include/eunit.hrl"). 117 | 118 | is_prime_test() -> 119 | ?assert(is_prime(17)), 120 | ?assert(is_prime(283)). 121 | 122 | composite_test() -> 123 | ?assertNot(is_prime(100)), 124 | ?assertNot(is_prime(105)). 125 | 126 | primes_upto_test() -> 127 | ?assertEqual([2, 3, 5, 7, 11, 13], primes_upto(15)). 128 | 129 | random_prime_test() -> 130 | ?assertEqual(103, random_prime(102, 105)). 131 | 132 | -ifdef(STOCHASTIC_TEST). 133 | 134 | pollard_p1_test_() -> [ 135 | ?_assertEqual(7001, pollard_p1(7451 * 7001, 7)), % 7450 = 2 5 5 149, 7000 = 2 2 2 5 5 5 7 136 | ?_assertEqual(5281, pollard_p1(3607 * 5281, 11)), % 3606 = 2 3 601, 5280 = 2 2 2 2 2 3 5 11 137 | ?_assertEqual(5741, pollard_p1(5939 * 5741, 41)), % 5938 = 2 2969, 5740 = 2 2 5 7 41 138 | ?_assertEqual(error, pollard_p1(5939 * 5741, 39)), % 39 < 41 and 2969 139 | ?_assertError(function_clause, pollard_p1(7001, 11))]. % 7001 is prime 140 | 141 | -endif. 142 | -------------------------------------------------------------------------------- /lib/ndpar/src/ravel.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc A problem of finding the shortest upravel. 3 | %% 4 | %% An unravel of a sequence `xs' is a bag of nonempty 5 | %% subsequences of `xs' that when shuffled together can give back `xs'. 6 | %% E.g. the letters of "accompany" can be unravelled into three lists: 7 | %% "acm", "an" and "copy". 8 | %% 9 | %% An unravel is called an upravel if all its component sequences 10 | %% are weakly increasing. Since each of "acm", "an" and "copy" is 11 | %% increasing, they give an upravel of "accompany", and so do 12 | %% "aany", "ccmp" and "o". 13 | %% Each nonempty sequence has at least one upravel, namely the 14 | %% upravel consisting of just singleton sequences. However, of all 15 | %% possible upravels, we want to determine one with shortest size. 16 | %% 17 | %% @reference [B1] Chapter 8, pp. 50–55 18 | %% 19 | -module(ravel). 20 | -author("Andrey Paramonov "). 21 | 22 | -export([supravel/1]). 23 | 24 | -import(lists, [foldr/3]). 25 | 26 | 27 | %% 28 | %% @doc Finds shortest upravel of the given sequence. 29 | %% 30 | -spec supravel([A]) -> [[A]] when A :: term(). 31 | supravel(Sequence) -> foldr(fun insert/2, [], Sequence). 32 | 33 | 34 | -spec insert(A, [[A]]) -> [[A]] when A :: term(). 35 | insert(X, []) -> [[X]]; 36 | insert(X, [[H | _] = Xs | Xss]) when X =< H -> [[X | Xs] | Xss]; 37 | insert(X, [Xs | Xss]) -> [Xs | insert(X, Xss)]. 38 | 39 | 40 | %% ============================================================================= 41 | %% Unit tests 42 | %% ============================================================================= 43 | 44 | -ifdef(TEST). 45 | -include_lib("eunit/include/eunit.hrl"). 46 | 47 | supravel_test_() -> 48 | [ 49 | ?_assertEqual(["aany", "ccmp", "o"], supravel("accompany")), 50 | ?_assertEqual( 51 | [[1, 1, 2, 3, 5, 7], [3, 4, 5, 5, 8, 9], [6], [9]], 52 | supravel([3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5, 8, 9, 7])) 53 | ]. 54 | 55 | -endif. 56 | -------------------------------------------------------------------------------- /lib/ndpar/src/rnd.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc PRNG functions. 3 | %% 4 | -module(rnd). 5 | -author("Andrey Paramonov"). 6 | 7 | -export([rand_bytes/1, rand_seed/1]). 8 | -export([random/2]). 9 | 10 | %% 11 | %% @doc Generates `N' bytes randomly uniform 0..255, 12 | %% and returns the result as an integer. 13 | %% 14 | %% Uses {@link rand:uniform/1} so that the result can 15 | %% be reproduced after reseeding PRNG with the same value. 16 | %% 17 | %% If reproducibility is not a requirement, use standard 18 | %% {@link crypto:strong_rand_bytes/1} instead. 19 | %% 20 | %% @see rand_seed/1 21 | %% 22 | -spec rand_bytes(N :: pos_integer()) -> pos_integer(). 23 | 24 | rand_bytes(N) -> 25 | Bytes = <<<<(rand:uniform(256) - 1):8>> || _ <- lists:seq(1, N)>>, 26 | binary:decode_unsigned(Bytes). 27 | 28 | %% 29 | %% @doc Seeds PRNG with `exsplus' algorithm and given seed and returns the state. 30 | %% 31 | %% @param Seed binary of size at least 30 bytes. 32 | %% 33 | %% @see rand:seed/2 34 | %% 35 | -spec rand_seed(Seed :: binary()) -> term(). 36 | 37 | rand_seed(Seed) when 30 =< size(Seed) -> 38 | L = bit_size(Seed), 39 | S = L div 24, 40 | R = L - 2 * S, 41 | <> = Seed, 42 | rand:seed(exsplus, {A1, A2, A3}). 43 | 44 | %% 45 | %% @doc Returns a random integer uniformly distributed in the interval `[L, U]'. 46 | %% 47 | -spec random(L :: integer(), U :: integer()) -> integer(). 48 | 49 | random(L, U) -> L + rand:uniform(U - L + 1) - 1. 50 | -------------------------------------------------------------------------------- /lib/ndpar/src/rsa_private_key.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Restoring RSA private key from its parts. 3 | %% 4 | %% RSA private key consists of four integer values `{p, q, t, d}'. 5 | %% The knowledge of any one of these values is sufficient to 6 | %% compute all the other three. 7 | %% 8 | %% Easy case: Attacker knows `p' (or `q'). Then 9 | %% ``` 10 | %% q = n / p 11 | %% t = (p - 1)(q - 1) / gcd(p - 1, q - 1) 12 | %% d = e^-1 (mod t)''' 13 | %% 14 | %% To play with the functions in this module, the following commands 15 | %% may be useful if you want to generate the real private keys: 16 | %% ``` 17 | %% $ openssl genrsa -out private.pem 2048 18 | %% $ openssl asn1parse -i -in private.pem 19 | %% ''' 20 | %% @reference [FSK1] Chapter 12.4.3. The Private Key. 21 | %% 22 | -module(rsa_private_key). 23 | -author("Andrey Paramonov"). 24 | 25 | -export([factorize_from_d/3, factorize_from_t/2]). 26 | 27 | %% 28 | %% @doc If an attacker knows `d', she can find `{p, q}' using this method. 29 | %% 30 | -spec factorize_from_d(N :: pos_integer(), E :: pos_integer(), D :: pos_integer()) -> 31 | {P :: pos_integer(), Q :: pos_integer()} | error. 32 | 33 | factorize_from_d(N, E, D) -> 34 | factorize_from_d(N, E, D, 1). 35 | 36 | factorize_from_d(N, E, D, Factor) -> 37 | case factorize_from_t(N, (E * D - 1) div Factor) of 38 | error -> factorize_from_d(N, E, D, Factor + 1); 39 | Result -> Result 40 | end. 41 | 42 | %% 43 | %% @doc If an attacker knows `t', she can find `{p, q}' using this method. 44 | %% 45 | -spec factorize_from_t(N :: pos_integer(), T :: pos_integer()) -> 46 | {P :: pos_integer(), Q :: pos_integer()} | error. 47 | 48 | factorize_from_t(N, T) -> 49 | case N div T of 50 | 0 -> error; 51 | GCD -> factorize_from_t(N, T, GCD) 52 | end. 53 | 54 | factorize_from_t(N, T, GCD) -> 55 | Phi = T * GCD, 56 | S = N - Phi + 1, 57 | factorize_from_s(N, S). 58 | 59 | factorize_from_s(_N, S) when S rem 2 =:= 1 -> error; 60 | factorize_from_s(N, S) -> 61 | S2 = S div 2, 62 | Desc = maths:isqrt(S2 * S2 - N), 63 | {P, Q} = {S2 - Desc, S2 + Desc}, 64 | case P * Q of 65 | N -> {P, Q}; 66 | _ -> error 67 | end. 68 | 69 | %% ============================================================================= 70 | %% Unit tests 71 | %% ============================================================================= 72 | 73 | -include_lib("eunit/include/eunit.hrl"). 74 | 75 | factorize_from_d_test() -> 76 | ?assertEqual({11, 13}, factorize_from_d(143, 7, 43)). 77 | 78 | factorize_from_t_test_() -> [ 79 | ?_assertEqual(error, factorize_from_t(143, 300)), 80 | ?_assertEqual(error, factorize_from_t(143, 150)), 81 | ?_assertEqual(error, factorize_from_t(143, 100)), 82 | ?_assertEqual(error, factorize_from_t(143, 75)), 83 | ?_assertEqual({11, 13}, factorize_from_t(143, 60))]. 84 | -------------------------------------------------------------------------------- /lib/ndpar/src/saddleback.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Saddleback search. 3 | %% 4 | %% Design a function `invert' that takes two arguments: 5 | %% a function `f: N × N ⟶ N', and 6 | %% a number `z ∈ N'. 7 | %% The value `invert(f, z)' is a list of all pairs `(x,y)' 8 | %% satisfying `f(x,y) = z'. 9 | %% `f' is strictly increasing in each argument. 10 | %% 11 | %% The solution should make as few evaluations of `f' as possible. 12 | %% 13 | %% See performance test results to choose the best algorithm 14 | %% for a given function. 15 | %% 16 | %% @reference [B1] Chapter 3, pp. 12–20 17 | %% 18 | -module(saddleback). 19 | -author("Andrey Paramonov "). 20 | 21 | -export([invert/2, invert1/2, invert2/2, invert3/2, invert4/2]). 22 | 23 | -import(lists, [seq/2]). 24 | 25 | -type natural() :: non_neg_integer(). 26 | 27 | %% 28 | %% @doc An alias to {@link invert4/2}. 29 | %% 30 | -spec invert(F, Z) -> [{X, Y}] when 31 | F :: fun((X, Y) -> Z), 32 | X :: natural(), 33 | Y :: natural(), 34 | Z :: natural(). 35 | 36 | invert(F, Z) -> invert4(F, Z). 37 | 38 | %% 39 | %% @doc An execution of the definition of the inverse function. 40 | %% The algorithm traverses the entire search square. 41 | %% ``` 42 | %% (0,Z) (Z,Z) 43 | %% ┌───────────────┐ 44 | %% ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ 45 | %% │ │ │ │ │ │ │ │ │ 46 | %% │ │ │ │ │ │ │ │ │ 47 | %% │ │ │ │ │ │ │ │ │ 48 | %% │ │ │ │ │ │ │ │ │ 49 | %% │ │ │ │ │ │ │ │ │ 50 | %% │ │ │ │ │ │ │ │ │ 51 | %% └─┴─┴─┴─┴─┴─┴─┴─┘ 52 | %% (0,0) (Z,0) 53 | %% ''' 54 | %% Inefficient (quadratic) but easy to understand. 55 | %% 56 | %% See Jack's algorithm, [B1] p. 12. 57 | %% 58 | -spec invert1(F, Z) -> [{X, Y}] when 59 | F :: fun((X, Y) -> Z), 60 | X :: natural(), 61 | Y :: natural(), 62 | Z :: natural(). 63 | 64 | invert1(F, Z) -> [{X, Y} || X <- seq(0, Z), Y <- seq(0, Z), F(X, Y) =:= Z]. 65 | 66 | %% 67 | %% @doc Basic algorithm. 68 | %% Traverses the diagonal of the search square, from `(0,Z)' to `(Z,0)'. 69 | %% ``` 70 | %% (0,Z) (Z,Z) 71 | %% ┌───────────────┐ 72 | %% ├─┐ │ 73 | %% │ │ │ 74 | %% │ └──┐ │ 75 | %% │ └─┐ │ 76 | %% │ └─┐ │ 77 | %% │ └─┐ │ 78 | %% │ └─┐ │ 79 | %% └────────────┴──┘ 80 | %% (0,0) (Z,0) 81 | %% ''' 82 | %% Runs in linear time in `Z'. 83 | %% 84 | %% See Anne's algorithm, [B1] p. 13. 85 | %% @end 86 | %% 87 | %% Not tail optimized. 88 | %% 89 | -spec invert2(F, Z) -> [{X, Y}] when 90 | F :: fun((X, Y) -> Z), 91 | X :: natural(), 92 | Y :: natural(), 93 | Z :: natural(). 94 | 95 | invert2(F, Z) -> find2({0, Z}, F, Z). 96 | 97 | find2({U, V}, _F, Z) when U > Z orelse V < 0 -> []; 98 | find2({U, V}, F, Z) -> 99 | Zf = F(U, V), 100 | if 101 | Zf < Z -> find2({U + 1, V}, F, Z); 102 | Zf =:= Z -> [{U, V} | find2({U + 1, V - 1}, F, Z)]; 103 | Zf > Z -> find2({U, V - 1}, F, Z) 104 | end. 105 | 106 | 107 | %% 108 | %% @doc Improvement to the basic algorithm by 109 | %% Gries, Dijkstra, and Backhouse. 110 | %% Replaces the search square with the search rectangle. 111 | %% ``` 112 | %% (0,Z) (Z,Z) 113 | %% ┌───────────────┐ 114 | %% │ │ 115 | %% │ │ 116 | %% M ├┬───────────┐ │ 117 | %% │└─┐ │ │ 118 | %% │ └─┐ │ │ 119 | %% │ └─┐ │ │ 120 | %% │ └─┐ │ │ 121 | %% └────────┴───┴──┘ 122 | %% (0,0) N (Z,0) 123 | %% ''' 124 | %% For some functions it can yield logarithmic performance. 125 | %% 126 | %% See Theo's algorithm, [B1] p. 14. 127 | %% 128 | -spec invert3(F, Z) -> [{X, Y}] when 129 | F :: fun((X, Y) -> Z), 130 | X :: natural(), 131 | Y :: natural(), 132 | Z :: natural(). 133 | 134 | invert3(F, Z) -> 135 | {N, M} = range(ext(F), Z), 136 | find3({0, M}, F, Z, N). 137 | 138 | find3({U, V}, _F, _Z, N) when U > N orelse V < 0 -> []; 139 | find3({U, V}, F, Z, _N) -> find2({U, V}, F, Z). 140 | 141 | %% 142 | %% @doc Finds better initial search range for function `F'. 143 | %% Narrows `(0,0) (0,Z) (Z,Z) (Z,0)' to `(0,0) (0,M) (N,M) (N,0)'. 144 | %% `F' must be extended to points (0,-1) and (-1,0). 145 | %% 146 | range(F, Z) -> 147 | { 148 | bsearch(fun(X) -> F(X, 0) end, {-1, Z + 1}, Z), 149 | bsearch(fun(Y) -> F(0, Y) end, {-1, Z + 1}, Z) 150 | }. 151 | 152 | %% 153 | %% @doc Binary search to determine `M' such that 154 | %%``` 155 | %% M = bsearch(G, {A, B}, Z), A ≤ M < B, G(M) ≤ Z < G(M + 1)''' 156 | %% 157 | bsearch(_, {A, B}, _) when A + 1 =:= B -> A; 158 | bsearch(G, {A, B}, Z) -> 159 | M = (A + B) div 2, 160 | bsearch(G, {A, B}, Z, M, G(M)). 161 | 162 | bsearch(G, {_, B}, Z, M, Gm) when Gm =< Z -> bsearch(G, {M, B}, Z); 163 | bsearch(G, {A, _}, Z, M, _) -> bsearch(G, {A, M}, Z). 164 | 165 | %% 166 | %% @doc Extends function F to points (0,-1) and (-1,0). 167 | %% 168 | ext(F) -> 169 | fun(X, Y) -> 170 | case {X, Y} of 171 | {0, -1} -> 0; 172 | {-1, 0} -> 0; 173 | _ -> F(X, Y) 174 | end 175 | end. 176 | 177 | 178 | %% 179 | %% @doc Divide and conquer version of {@link invert3/2}. 180 | %% ``` 181 | %% (0,Z) (Z,Z) 182 | %% ┌───────────────┐ 183 | %% │ │ 184 | %% ├───┐ │ 185 | %% ├───┘. │ 186 | %% │ ┌─┐ │ 187 | %% │ └─┘ . │ 188 | %% │ ┌──┐│ 189 | %% └───────────┴──┴┘ 190 | %% (0,0) (Z,0) 191 | %% ''' 192 | %% Asymptotically optimal saddleback search algorithm. 193 | %% 194 | %% See Mary's algorithm, [B1] p. 18. 195 | %% @end 196 | %% 197 | %% Not tail optimized though and with list concatenations. 198 | %% 199 | -spec invert4(F, Z) -> [{X, Y}] when 200 | F :: fun((X, Y) -> Z), 201 | X :: natural(), 202 | Y :: natural(), 203 | Z :: natural(). 204 | 205 | invert4(F, Z) -> 206 | {N, M} = range(ext(F), Z), 207 | find4({0, M}, {N, 0}, F, Z). 208 | 209 | find4({U, V}, {R, S}, _F, _Z) when U > R orelse V < S -> []; 210 | find4({U, V}, {R, S}, F, Z) -> 211 | P = (U + R) div 2, 212 | Q = (V + S) div 2, 213 | RFind = fun(X) -> 214 | case F(X, Q) of 215 | Z -> [{X, Q} | find4({U, V}, {X - 1, Q + 1}, F, Z)]; 216 | _ -> find4({U, V}, {X, Q + 1}, F, Z) 217 | end ++ 218 | find4({X + 1, Q - 1}, {R, S}, F, Z) 219 | end, 220 | CFind = fun(Y) -> 221 | find4({U, V}, {P - 1, Y + 1}, F, Z) ++ 222 | case F(P, Y) of 223 | Z -> [{P, Y} | find4({P + 1, Y - 1}, {R, S}, F, Z)]; 224 | _ -> find4({P + 1, Y}, {R, S}, F, Z) 225 | end 226 | end, 227 | if 228 | V - S =< R - U -> RFind(bsearch(fun(X) -> F(X, Q) end, {U - 1, R + 1}, Z)); 229 | true -> CFind(bsearch(fun(Y) -> F(P, Y) end, {S - 1, V + 1}, Z)) 230 | end. 231 | 232 | %% ============================================================================= 233 | %% Unit tests 234 | %% ============================================================================= 235 | 236 | -ifdef(TEST). 237 | -include_lib("eunit/include/eunit.hrl"). 238 | 239 | invert_test_() -> 240 | F = fun(X, Y) -> X + Y end, 241 | Result = [{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}], 242 | [ 243 | ?_assertEqual(Result, invert1(F, 5)), 244 | ?_assertEqual(Result, invert2(F, 5)), 245 | ?_assertEqual(Result, invert3(F, 5)), 246 | ?_assertEqual([{3, 2}, {1, 4}, {0, 5}, {2, 3}, {5, 0}, {4, 1}], invert4(F, 5)) 247 | ]. 248 | 249 | ext_test_() -> 250 | F = fun(X, Y) -> X + Y end, 251 | G = ext(F), 252 | [ 253 | ?_assertEqual(0, G(0, -1)), 254 | ?_assertEqual(0, G(-1, 0)), 255 | ?_assertEqual(F(3, 4), G(3, 4)) 256 | ]. 257 | 258 | -endif. 259 | -------------------------------------------------------------------------------- /lib/ndpar/test/rsa_private_key_tests.erl: -------------------------------------------------------------------------------- 1 | -module(rsa_private_key_tests). 2 | -import(rsa_private_key, [factorize_from_d/3, factorize_from_t/2]). 3 | -include_lib("eunit/include/eunit.hrl"). 4 | 5 | real_rsa_test_() -> 6 | N = 16#C272F8EF3CF73F5DD9F0CC33632DBBB27C6BDAF7323E977A1874E7884FD6E409BC937687D7C1AD6D272FB514CC24FD3798D32C30DAA947472865BEA2D2F5F952388AD19E87299E819CBB91F510F6547DA36DB552780CBFB24D3859C662F92F9DB1679B6E3F47C9DBAE23E1E228B53115D785976FED1B31B40427EE3BC68B0F64BEE9A7EE292DB9D13986F3F652639584C366727C16CEFE271FC6DD4E5E2060E5111996E694D750C7C0DD7F3EAB3BD7B08FFFE826AE9964528221790A008088F7DAB1647B5D9F689C0ABF118048A5AC40807254A4F98567A36E6F96E4D17F8476CFA216325A51A80DAF7D0238E5BB3C348AE37B52DFF26155064421A1D23CE08B, 7 | E = 16#010001, 8 | P = 16#DBFD0613F6F3749158B08346E0073FE5DF90ECBFECBDFECE79CB1E5038101555B58A9CEB2B80E52B1C5EE080FEF327C513F4B43FE49D70008A15E95C00F387BABDEB93007C52EC50A18754EAF7AA823549292B5A59A0AAFC0D0E5E7BF8D616A2ADF34648CD79C59D34E902FA2ED5EC4D6E3F15D2F46797AD777EA29226414EDD, 9 | Q = 16#E247AF0372636C9CFCEF2F06E65358BDD7602B679691FD8A6C78A687B97FDC87121F3609A1C8F0ADC5BBA5B6A1FF2BA49DE217E242AD67863274109CD028040CFB2BB383C9D4AC6280FABF28C8C3155DDC507DE9472399B6C536A624C50F4755D96014150DE11FD24E7710DC484CE923A05F34DDDB690B4A62C35961FE57D287, 10 | T = 16#61397C779E7B9FAEECF86619B196DDD93E35ED7B991F4BBD0C3A73C427EB7204DE49BB43EBE0D6B69397DA8A66127E9BCC6996186D54A3A39432DF51697AFCA91C4568CF4394CF40CE5DC8FA887B2A3ED1B6DAA93C065FD9269C2CE3317C97CED8B3CDB71FA3E4EDD711F0F1145A988AEBC2CBB7F68D98DA0213F71DE34587B18052796B5FEB6C5171F3A0D446047E70863AAD2A49BF80E71CC18C3B3648378424B7E1F8E3C6BD776F617C838524C2236F148E0243A74665E2CBBF8897B27E9810CD0EFB8BBBE7F4741E7EB6441C0A56AD7C55B0AC6091784E15492209CD133F24275DEA3F7B614F160E7731374C3361BE2298510810DF2E960112D6D6D1DF94, 11 | D = 16#A234203E094042A6BA5FA92790AB1CB0C6237E73C880F8010C97B070401185062E3D6099EEBC0C3C4A7CFC740DDB293390934F80AD569A33DC9A0B0D6E276BC44F90554E05780D5617754C4AFECC4D2CC5008649F604C4802AF43FC742D69506D96F10F4456B5012A5D01EE4768AB7187E415D532B9A0CBB1AE068558AC8839C557443722A734121983557CB0E8D8CCCFA8486469907506D44027C8C850172ED99BCD6DA40595FDC150CD5892EE6A6A62B3A65B9CD5914100392DD976B4EB5F9C97DF353244B78AED3D3D49F0D7EA298FD9303FB5F7B34F73B7E33A700270DC737F01DB523F819DEB1F3AC5385E831890E87F50AABA44A551499F1EE50339109, 12 | [ 13 | ?_assertEqual({P, Q}, factorize_from_d(N, E, D)), 14 | ?_assertEqual({P, Q}, factorize_from_t(N, T)) 15 | ]. 16 | -------------------------------------------------------------------------------- /lib/ndpar/test/saddleback_tests.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @doc Performance tests of the `saddleback' search algorithm. 3 | %% 4 | %% @reference [B1] pp. 19–20 5 | %% ``` 6 | %% > fprof:apply(saddleback, invert4, [fun(X, Y) -> 3 * X + 27 * Y + Y * Y end, 5000]). 7 | %% > fprof:profile(). 8 | %% > fprof:analyse([{dest, "invert42.txt"}]). 9 | %% 10 | %% > fprof:apply(saddleback, invert3, [fun(X, Y) -> 3 * X + 27 * Y + Y * Y end, 5000]). 11 | %% > fprof:profile(). 12 | %% > fprof:analyse([{dest, "invert32.txt"}]). 13 | %% ''' 14 | -module(saddleback_tests). 15 | -author("Andrey Paramonov "). 16 | 17 | f0(X, Y) -> maths:pow(2, Y) * (2 * X + 1) - 1. 18 | 19 | f1(X, Y) -> X * maths:pow(2, X) + Y * maths:pow(2, Y) + 2 * X + Y. 20 | 21 | f2(X, Y) -> 3 * X + 27 * Y + Y * Y. 22 | 23 | f3(X, Y) -> X * X + Y * Y + X + Y. 24 | 25 | f4(X, Y) -> X + maths:pow(2, Y) + Y + 1. 26 | 27 | f5(X, Y) -> X + Y. 28 | 29 | -include_lib("eunit/include/eunit.hrl"). 30 | 31 | ifuns() -> [ 32 | fun saddleback:invert2/2, 33 | fun saddleback:invert3/2, 34 | fun saddleback:invert4/2 35 | ]. 36 | 37 | %% 38 | %% Count the number of invocations of function `f' 39 | %% under different versions of the inversion algorithm. 40 | %% 41 | count_test_() -> 42 | cprof:start(), 43 | 44 | F0 = [prof(IFun, fun f0/2, f0, 5000) || IFun <- ifuns()], 45 | F1 = [prof(IFun, fun f1/2, f1, 5000) || IFun <- ifuns()], 46 | F2 = [prof(IFun, fun f2/2, f2, 5000) || IFun <- ifuns()], 47 | F3 = [prof(IFun, fun f3/2, f3, 5000) || IFun <- ifuns()], 48 | F4 = [prof(IFun, fun f4/2, f4, 5000) || IFun <- ifuns()], 49 | 50 | cprof:stop(), 51 | [ % inv2 inv3 inv4 52 | ?_assertEqual([7501, 2538, 129], F0), % 2ʸ(2x + 1) - 1 53 | ?_assertEqual([5011, 44, 46], F1), % x 2ˣ + y 2ʸ + 2x + y 54 | ?_assertEqual([6668, 1751, 464], F2), % 3x + 27y + y² 55 | ?_assertEqual([5068, 164, 204], F3), % x² + y² + x + y 56 | ?_assertEqual([9987, 5024, 143], F4) % x + 2ʸ + y + 1 57 | ]. 58 | 59 | prof(IFun, Fun, F, Z) -> 60 | cprof:restart(), 61 | IFun(Fun, Z), 62 | fun_count_from(cprof:analyse(?MODULE), F). 63 | 64 | fun_count_from({?MODULE, _, FunCalls}, Fun) -> fun_count(FunCalls, Fun). 65 | 66 | fun_count([], _) -> 0; 67 | fun_count([{{_, Fun, _}, N} | _], Fun) -> N; 68 | fun_count([_ | Cs], Fun) -> fun_count(Cs, Fun). 69 | 70 | 71 | f5_test_() -> 72 | cprof:start(), 73 | F5 = [prof(IFun, fun f5/2, f5, 5) || IFun <- [fun saddleback:invert1/2 | ifuns()]], 74 | cprof:stop(), 75 | [?_assertEqual([36, 6, 12, 22], F5)]. 76 | -------------------------------------------------------------------------------- /mycache/README: -------------------------------------------------------------------------------- 1 | INSTALLING DATABASE 2 | 3 | (master@macBook)1> mnesia:create_schema([master@macBook, slave1@macBook, slave2@macBook]). 4 | (master@macBook)2> application:start(mnesia). 5 | (slave1@macBook)1> application:start(mnesia). 6 | (slave2@macBook)1> application:start(mnesia). 7 | (master@macBook)3> rd(mycache, {key, value}). 8 | (master@macBook)4> mnesia:create_table(mycache, [{attributes, record_info(fields, mycache)}, {disc_only_copies, [master@macBook]}, {ram_copies, [slave1@macBook, slave2@macBook]}]). 9 | 10 | (slave2@macBook)2> application:stop(mnesia). 11 | (slave1@macBook)2> application:stop(mnesia). 12 | (master@macBook)5> application:stop(mnesia). 13 | 14 | WORKING WITH CACHE 15 | 16 | master@macBook$ ./start-master.sh 17 | slave1@macBook$ ./start-slave1.sh 18 | slave2@macBook$ ./start-slave2.sh 19 | -------------------------------------------------------------------------------- /mycache/ebin/mycache.app: -------------------------------------------------------------------------------- 1 | {application, mycache, 2 | [{description, "Distributed cache"}, 3 | {vsn, "1.0"}, 4 | {modules, [mycache, mycache_sup, mycache_app]}, 5 | {registered, [mycache, mycache_sup]}, 6 | {applications, [kernel, stdlib]}, 7 | {env, []}, 8 | {mod, {mycache_app, []}}]}. 9 | -------------------------------------------------------------------------------- /mycache/ebin/start-master.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | erl -sname master -s mycache_boot 4 | 5 | -------------------------------------------------------------------------------- /mycache/ebin/start-slave1.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | erl -noshell -sname slave1 -s application start mycache 4 | 5 | -------------------------------------------------------------------------------- /mycache/ebin/start-slave2.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | erl -sname slave2 -s mycache_boot 4 | 5 | -------------------------------------------------------------------------------- /mycache/java/pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | com.ndpar 5 | erlcache 6 | jar 7 | 1.0-SNAPSHOT 8 | 9 | 10 | 11 | 12 | org.apache.maven.plugins 13 | maven-compiler-plugin 14 | 2.0.2 15 | 16 | 1.6 17 | 1.6 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | com.ericsson.jinterface 26 | otp-erlang 27 | 1.5.1 28 | 29 | 30 | commons-io 31 | commons-io 32 | 1.4 33 | test 34 | 35 | 36 | junit 37 | junit 38 | 4.13.1 39 | test 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /mycache/java/src/main/java/com/ndpar/erlcache/ErlStringMap.java: -------------------------------------------------------------------------------- 1 | package com.ndpar.erlcache; 2 | 3 | import java.util.Collection; 4 | import java.util.Map; 5 | import java.util.Set; 6 | 7 | import com.ericsson.otp.erlang.*; 8 | 9 | public class ErlStringMap implements Map { 10 | 11 | private final OtpSelf self; 12 | private final OtpPeer other; 13 | private final String cacheModule; 14 | 15 | private OtpConnection connection; 16 | 17 | public ErlStringMap(String client, String cookie, String serverNode, String cacheModule) { 18 | try { 19 | self = new OtpSelf(client, cookie); 20 | other = new OtpPeer(serverNode); 21 | this.cacheModule = cacheModule; 22 | } catch (Exception e) { 23 | throw new RuntimeException(e.getMessage(), e); 24 | } 25 | } 26 | 27 | public void open() { 28 | try { 29 | connection = self.connect(other); 30 | } catch (Exception e) { 31 | throw new RuntimeException(e.getMessage(), e); 32 | } 33 | } 34 | 35 | public void close() { 36 | try { 37 | connection.close(); 38 | } catch (Exception e) { 39 | throw new RuntimeException(e.getMessage(), e); 40 | } 41 | } 42 | 43 | public String put(String key, String value) { 44 | return remoteCall("put", key, value); 45 | } 46 | 47 | public String get(Object key) { 48 | return remoteCall("get", (String) key); 49 | } 50 | 51 | public String remove(Object key) { 52 | return remoteCall("remove", (String) key); 53 | } 54 | 55 | private String remoteCall(String method, String... args) { 56 | try { 57 | connection.sendRPC(cacheModule, method, stringsToErlangStrings(args)); 58 | OtpErlangObject received = connection.receiveRPC(); 59 | return parse(received); 60 | } catch (Exception e) { 61 | throw new RuntimeException(e.getMessage(), e); 62 | } 63 | } 64 | 65 | private OtpErlangObject[] stringsToErlangStrings(String[] strings) { 66 | OtpErlangObject[] result = new OtpErlangObject[strings.length]; 67 | for (int i = 0; i < strings.length; i++) result[i] = new OtpErlangString(strings[i]); 68 | return result; 69 | } 70 | 71 | private String parse(OtpErlangObject otpObj) { 72 | if (otpObj instanceof OtpErlangAtom) { 73 | OtpErlangAtom atom = (OtpErlangAtom) otpObj; 74 | if (atom.atomValue().equals("null")) return null; 75 | else throw new IllegalArgumentException("Only atom null is supported"); 76 | 77 | } else if (otpObj instanceof OtpErlangString) { 78 | OtpErlangString str = (OtpErlangString) otpObj; 79 | return str.stringValue(); 80 | } 81 | throw new IllegalArgumentException("Unexpected type " + otpObj.getClass().getName()); 82 | } 83 | 84 | 85 | public void clear() { 86 | throw new UnsupportedOperationException("Not implemented yet"); 87 | } 88 | 89 | public boolean containsKey(Object key) { 90 | throw new UnsupportedOperationException("Not implemented yet"); 91 | } 92 | 93 | public boolean containsValue(Object value) { 94 | throw new UnsupportedOperationException("Not implemented yet"); 95 | } 96 | 97 | public Set entrySet() { 98 | throw new UnsupportedOperationException("Not implemented yet"); 99 | } 100 | 101 | public boolean isEmpty() { 102 | throw new UnsupportedOperationException("Not implemented yet"); 103 | } 104 | 105 | public Set keySet() { 106 | throw new UnsupportedOperationException("Not implemented yet"); 107 | } 108 | 109 | public void putAll(Map m) { 110 | throw new UnsupportedOperationException("Not implemented yet"); 111 | } 112 | 113 | public int size() { 114 | throw new UnsupportedOperationException("Not implemented yet"); 115 | } 116 | 117 | public Collection values() { 118 | throw new UnsupportedOperationException("Not implemented yet"); 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /mycache/java/src/test/java/com/ndpar/erlcache/ErlStringMapTest.java: -------------------------------------------------------------------------------- 1 | package com.ndpar.erlcache; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import java.io.File; 6 | import java.util.Map; 7 | 8 | import org.apache.commons.io.FileUtils; 9 | import org.junit.After; 10 | import org.junit.Before; 11 | import org.junit.BeforeClass; 12 | import org.junit.Ignore; 13 | import org.junit.Test; 14 | 15 | public class ErlStringMapTest { 16 | 17 | private static String cookie; 18 | private ErlStringMap map; 19 | 20 | @BeforeClass 21 | public static void setUpClass() throws Exception { 22 | cookie = FileUtils.readFileToString(new File("/Users/andrey/.erlang.cookie")); 23 | } 24 | 25 | @Before 26 | public void setUp() { 27 | map = new ErlStringMap("client1", cookie, "slave1@macBook", "mycache"); 28 | map.open(); 29 | } 30 | 31 | @After 32 | public void tearDown() { 33 | map.close(); 34 | } 35 | 36 | @Test 37 | public void getReturnsNullFromEmptyMap() { 38 | Object result = map.get("testing"); 39 | assertNull(result); 40 | } 41 | 42 | @Test 43 | public void getPutRemoveReturnCorrectValues() { 44 | assertNull(map.put("foo", "bar")); 45 | assertEquals("bar", map.get("foo")); 46 | assertEquals("bar", map.put("foo", "baz")); 47 | assertEquals("baz", map.remove("foo")); 48 | } 49 | 50 | @Test 51 | public void removeReturnsNullIfNoSuchEntry() { 52 | Object result = map.remove("notexsist"); 53 | assertNull(result); 54 | } 55 | 56 | @Test @Ignore("long running dirty test") 57 | public void writePerformance() { 58 | int count = 1000; 59 | long start = System.currentTimeMillis(); 60 | for (Integer i = 0; i < count; i++) map.put(i.toString(), i.toString()); 61 | long duration = System.currentTimeMillis() - start; 62 | System.out.println(count + " write operations take: " + duration + "ms. " + (1.0 * duration/count) + "ms per operation"); 63 | } 64 | 65 | @Test @Ignore("long running test") 66 | public void getPerformance() { 67 | int count = 1000; 68 | long start = System.currentTimeMillis(); 69 | for (Integer i = 0; i < count; i++) map.get(i.toString()); 70 | long duration = System.currentTimeMillis() - start; 71 | System.out.println(count + " get operations take: " + duration + "ms. " + (1.0 * duration/count) + "ms per operation"); 72 | } 73 | 74 | @Test @Ignore("long running dirty test") 75 | public void removePerformance() { 76 | int count = 1000; 77 | long start = System.currentTimeMillis(); 78 | for (Integer i = 0; i < count; i++) map.remove(i.toString()); 79 | long duration = System.currentTimeMillis() - start; 80 | System.out.println(count + " remove operations take: " + duration + "ms. " + (1.0 * duration/count) + "ms per operation"); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /mycache/src/mycache.erl: -------------------------------------------------------------------------------- 1 | -module(mycache). 2 | -export([start/0, stop/0]). 3 | -export([put/2, get/1, remove/1]). 4 | -export([init/1, terminate/2, handle_call/3, handle_cast/2]). 5 | -behaviour(gen_server). 6 | -include("mycache.hrl"). 7 | 8 | % Start/stop functions 9 | 10 | start() -> 11 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 12 | 13 | stop() -> 14 | gen_server:cast(?MODULE, stop). 15 | 16 | % Functional interface 17 | 18 | put(Key, Value) -> 19 | gen_server:call(?MODULE, {put, Key, Value}). 20 | 21 | get(Key) -> 22 | gen_server:call(?MODULE, {get, Key}). 23 | 24 | remove(Key) -> 25 | gen_server:call(?MODULE, {remove, Key}). 26 | 27 | % Callback functions 28 | 29 | init(_) -> 30 | application:start(mnesia), 31 | mnesia:wait_for_tables([mycache], infinity), 32 | {ok, []}. 33 | 34 | terminate(_Reason, _State) -> 35 | application:stop(mnesia). 36 | 37 | handle_cast(stop, State) -> 38 | {stop, normal, State}. 39 | 40 | handle_call({put, Key, Value}, _From, State) -> 41 | Rec = #mycache{key = Key, value = Value}, 42 | F = fun() -> 43 | case mnesia:read(mycache, Key) of 44 | [] -> 45 | mnesia:write(Rec), 46 | null; 47 | [#mycache{value = OldValue}] -> 48 | mnesia:write(Rec), 49 | OldValue 50 | end 51 | end, 52 | {atomic, Result} = mnesia:transaction(F), 53 | {reply, Result, State}; 54 | 55 | handle_call({get, Key}, _From, State) -> 56 | case mnesia:dirty_read({mycache, Key}) of 57 | [#mycache{value = Value}] -> {reply, Value, []}; 58 | _ -> {reply, null, State} 59 | end; 60 | 61 | handle_call({remove, Key}, _From, State) -> 62 | F = fun() -> 63 | case mnesia:read(mycache, Key) of 64 | [] -> null; 65 | [#mycache{value = Value}] -> 66 | mnesia:delete({mycache, Key}), 67 | Value 68 | end 69 | end, 70 | {atomic, Result} = mnesia:transaction(F), 71 | {reply, Result, State}. 72 | -------------------------------------------------------------------------------- /mycache/src/mycache.hrl: -------------------------------------------------------------------------------- 1 | -record(mycache, {key, value}). -------------------------------------------------------------------------------- /mycache/src/mycache_app.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% @author Andrey Paramonov 3 | %% @doc Application module for Distributed Cache 4 | %% @copyright 2009 Andrey Paramonov 5 | %% 6 | -module(mycache_app). 7 | -export([start/2, stop/1]). 8 | -behaviour(application). 9 | 10 | %% 11 | %% @doc Starts application 12 | %% @spec start(Type, StartArgs) -> Result 13 | %% Result = {ok,Pid} | ignore | {error,Error} 14 | %% Pid = pid() 15 | %% Error = {already_started,Pid} | shutdown | term() 16 | %% 17 | start(_Type, _StartArgs) -> 18 | mycache_sup:start(). 19 | 20 | %% 21 | %% @doc Stops application 22 | %% @spec stop(State) -> ok 23 | %% 24 | stop(_State) -> 25 | ok. 26 | -------------------------------------------------------------------------------- /mycache/src/mycache_boot.erl: -------------------------------------------------------------------------------- 1 | -module(mycache_boot). 2 | -export([start/0]). 3 | 4 | start() -> 5 | application:start(mycache). 6 | -------------------------------------------------------------------------------- /mycache/src/mycache_sup.erl: -------------------------------------------------------------------------------- 1 | -module(mycache_sup). 2 | -export([start/0]). 3 | -export([init/1]). 4 | -behaviour(supervisor). 5 | 6 | start() -> 7 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 8 | 9 | init(_) -> 10 | MycacheWorker = {mycache, {mycache, start, []}, permanent, 30000, worker, [mycache, mnesia]}, 11 | {ok, {{one_for_all, 5, 3600}, [MycacheWorker]}}. 12 | -------------------------------------------------------------------------------- /mydb/bin/mydb.script: -------------------------------------------------------------------------------- 1 | %% script generated at {2009,12,11} {22,43,9} 2 | {script, 3 | {"In-memory Database","R1"}, 4 | [{preLoaded, 5 | [erl_prim_loader,erlang,init,otp_ring0,prim_file,prim_inet,prim_zip, 6 | zlib]}, 7 | {progress,preloaded}, 8 | {path, 9 | ["/usr/local/lib/erlang/lib/kernel-2.13.3/ebin", 10 | "/usr/local/lib/erlang/lib/stdlib-1.16.3/ebin"]}, 11 | {primLoad,[error_handler]}, 12 | {kernel_load_completed}, 13 | {progress,kernel_load_completed}, 14 | {path,["/usr/local/lib/erlang/lib/kernel-2.13.3/ebin"]}, 15 | {primLoad, 16 | [application,application_controller,application_master, 17 | application_starter,auth,code,code_server,disk_log,disk_log_1, 18 | disk_log_server,disk_log_sup,dist_ac,dist_util,erl_boot_server, 19 | erl_ddll,erl_distribution,erl_epmd,erl_reply,error_logger, 20 | erts_debug,file,file_io_server,file_server,gen_sctp,gen_tcp,gen_udp, 21 | global,global_group,global_search,group,heart,hipe_unified_loader, 22 | inet,inet6_sctp,inet6_tcp,inet6_tcp_dist,inet6_udp,inet_config, 23 | inet_db,inet_dns,inet_gethost_native,inet_hosts,inet_parse,inet_res, 24 | inet_sctp,inet_tcp,inet_tcp_dist,inet_udp,kernel,kernel_config,net, 25 | net_adm,net_kernel,os,packages,pg2,ram_file,rpc,seq_trace, 26 | standard_error,user,user_drv,user_sup,wrap_log_reader]}, 27 | {path,["/usr/local/lib/erlang/lib/stdlib-1.16.3/ebin"]}, 28 | {primLoad, 29 | [array,base64,beam_lib,c,calendar,dets,dets_server,dets_sup, 30 | dets_utils,dets_v8,dets_v9,dict,digraph,digraph_utils,edlin, 31 | edlin_expand,epp,erl_bits,erl_compile,erl_eval,erl_expand_records, 32 | erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp,erl_scan, 33 | erl_tar,error_logger_file_h,error_logger_tty_h,escript,ets, 34 | eval_bits,file_sorter,filelib,filename,gb_sets,gb_trees,gen, 35 | gen_event,gen_fsm,gen_server,io,io_lib,io_lib_format,io_lib_fread, 36 | io_lib_pretty,lib,lists,log_mf_h,math,ms_transform,orddict,ordsets, 37 | otp_internal,pg,pool,proc_lib,proplists,qlc,qlc_pt,queue,random,re, 38 | regexp,sets,shell,shell_default,slave,sofs,string,supervisor, 39 | supervisor_bridge,sys,timer,unicode,win32reg,zip]}, 40 | {path,["/Users/andrey/projects/ndpar/erlang/mydb/ebin"]}, 41 | {primLoad,[db,mydb,mydb_app,mydb_sup]}, 42 | {progress,modules_loaded}, 43 | {path, 44 | ["/usr/local/lib/erlang/lib/kernel-2.13.3/ebin", 45 | "/usr/local/lib/erlang/lib/stdlib-1.16.3/ebin", 46 | "/Users/andrey/projects/ndpar/erlang/mydb/ebin"]}, 47 | {kernelProcess,heart,{heart,start,[]}}, 48 | {kernelProcess,error_logger,{error_logger,start_link,[]}}, 49 | {kernelProcess,application_controller, 50 | {application_controller,start, 51 | [{application,kernel, 52 | [{description,"ERTS CXC 138 10"}, 53 | {vsn,"2.13.3"}, 54 | {id,[]}, 55 | {modules, 56 | [application,application_controller,application_master, 57 | application_starter,auth,code,packages,code_server, 58 | dist_util,erl_boot_server,erl_distribution,erl_reply, 59 | error_handler,error_logger,file,file_server, 60 | file_io_server,global,global_group,global_search, 61 | group,heart,hipe_unified_loader,inet6_tcp, 62 | inet6_tcp_dist,inet6_udp,inet6_sctp,inet_config, 63 | inet_hosts,inet_gethost_native,inet_tcp_dist,kernel, 64 | kernel_config,net,net_adm,net_kernel,os,ram_file,rpc, 65 | user,user_drv,user_sup,disk_log,disk_log_1, 66 | disk_log_server,disk_log_sup,dist_ac,erl_ddll, 67 | erl_epmd,erts_debug,gen_tcp,gen_udp,gen_sctp,inet, 68 | inet_db,inet_dns,inet_parse,inet_res,inet_tcp, 69 | inet_udp,inet_sctp,pg2,seq_trace,standard_error, 70 | wrap_log_reader]}, 71 | {registered, 72 | [application_controller,erl_reply,auth,boot_server, 73 | code_server,disk_log_server,disk_log_sup, 74 | erl_prim_loader,error_logger,file_server_2, 75 | fixtable_server,global_group,global_name_server,heart, 76 | init,kernel_config,kernel_sup,net_kernel,net_sup,rex, 77 | user,os_server,ddll_server,erl_epmd,inet_db,pg2]}, 78 | {applications,[]}, 79 | {included_applications,[]}, 80 | {env,[{error_logger,tty}]}, 81 | {start_phases,undefined}, 82 | {maxT,infinity}, 83 | {maxP,infinity}, 84 | {mod,{kernel,[]}}]}]}}, 85 | {progress,init_kernel_started}, 86 | {apply, 87 | {application,load, 88 | [{application,stdlib, 89 | [{description,"ERTS CXC 138 10"}, 90 | {vsn,"1.16.3"}, 91 | {id,[]}, 92 | {modules, 93 | [array,base64,beam_lib,c,calendar,dets,dets_server, 94 | dets_sup,dets_utils,dets_v8,dets_v9,dict,digraph, 95 | digraph_utils,edlin,edlin_expand,epp,eval_bits, 96 | erl_bits,erl_compile,erl_eval,erl_expand_records, 97 | erl_internal,erl_lint,erl_parse,erl_posix_msg,erl_pp, 98 | erl_scan,erl_tar,error_logger_file_h, 99 | error_logger_tty_h,escript,ets,file_sorter,filelib, 100 | filename,gb_trees,gb_sets,gen,gen_event,gen_fsm, 101 | gen_server,io,io_lib,io_lib_format,io_lib_fread, 102 | io_lib_pretty,lib,lists,log_mf_h,math,ms_transform, 103 | orddict,ordsets,otp_internal,pg,pool,proc_lib, 104 | proplists,qlc,qlc_pt,queue,random,re,regexp,sets, 105 | shell,shell_default,slave,sofs,string,supervisor, 106 | supervisor_bridge,sys,timer,unicode,win32reg,zip]}, 107 | {registered, 108 | [timer_server,rsh_starter,take_over_monitor, 109 | pool_master,dets]}, 110 | {applications,[kernel]}, 111 | {included_applications,[]}, 112 | {env,[]}, 113 | {start_phases,undefined}, 114 | {maxT,infinity}, 115 | {maxP,infinity}]}]}}, 116 | {apply, 117 | {application,load, 118 | [{application,mydb, 119 | [{description,"In-memory Database"}, 120 | {vsn,"1.0"}, 121 | {id,[]}, 122 | {modules,[db,mydb,mydb_sup,mydb_app]}, 123 | {registered,[mydb,mydb_sup]}, 124 | {applications,[kernel,stdlib]}, 125 | {included_applications,[]}, 126 | {env,[]}, 127 | {start_phases,undefined}, 128 | {maxT,infinity}, 129 | {maxP,infinity}, 130 | {mod,{mydb_app,[]}}]}]}}, 131 | {progress,applications_loaded}, 132 | {apply,{application,start_boot,[kernel,permanent]}}, 133 | {apply,{application,start_boot,[stdlib,permanent]}}, 134 | {apply,{application,start_boot,[mydb,permanent]}}, 135 | {apply,{c,erlangrc,[]}}, 136 | {progress,started}]}. 137 | -------------------------------------------------------------------------------- /mydb/bin/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | erl -boot mydb 4 | 5 | -------------------------------------------------------------------------------- /mydb/ebin/mydb.app: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.292. 3 | %% Exercise 12-3: Database Application 4 | %% 5 | {application, mydb, 6 | [{description, "In-memory Database"}, 7 | {vsn, "1.0"}, 8 | {modules, [db, mydb, mydb_sup, mydb_app]}, 9 | {registered, [mydb, mydb_sup]}, 10 | {applications, [kernel, stdlib]}, 11 | {env, []}, 12 | {mod, {mydb_app, []}}]}. 13 | -------------------------------------------------------------------------------- /mydb/ebin/mydb.rel: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.289. 3 | %% 4 | {release, {"In-memory Database","R1"}, {erts, "5.7.3"}, 5 | [{kernel,"2.13.3"}, 6 | {stdlib,"1.16.3"}, 7 | {mydb,"1.0"}]}. 8 | -------------------------------------------------------------------------------- /mydb/src/db.erl: -------------------------------------------------------------------------------- 1 | %% Author: Andrey Paramonov 2 | %% Created: Nov 7, 2009 3 | %% Description: Simple DB (See "Erlang Programming", p.83) 4 | -module(db). 5 | -export([new/0, destroy/1, write/3, read/2, match/2, delete/2]). 6 | 7 | %% 8 | %% Returns the list of keys for which Element is stored in the database 9 | %% 10 | match(_, []) -> []; 11 | match(Element, [{Key, Element} | Tail]) -> [Key | match(Element, Tail)]; 12 | match(Element, [_ | Tail]) -> match(Element, Tail). 13 | 14 | %% 15 | %% Reads Element with the specified Key from database Db 16 | %% 17 | read(_, []) -> {error, instance}; 18 | read(Key, [{Key, Element} | _]) -> {ok, Element}; 19 | read(Key, [_ | Tail]) -> read(Key, Tail). 20 | 21 | %% 22 | %% Deletes Element with specified Key from datase Db 23 | %% 24 | delete(_, []) -> []; 25 | delete(Key, [{Key, _} | Tail]) -> Tail; 26 | delete(Key, [Entry | Tail]) -> [Entry | delete(Key, Tail)]. 27 | 28 | %% 29 | %% Writes tuple {Key, Element} into database Db 30 | %% 31 | write(Key, Element, Db) -> [{Key, Element} | delete(Key, Db)]. 32 | 33 | %% 34 | %% Drops the database 35 | %% 36 | destroy(_Db) -> ok. 37 | 38 | %% 39 | %% Creates new database 40 | %% 41 | new() -> []. 42 | -------------------------------------------------------------------------------- /mydb/src/mydb.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.291. 3 | %% Exercise 12-1: Database Server 4 | %% 5 | %% 1> mydb:start(). 6 | %% {ok,<0.33.0>} 7 | %% 2> mydb:write(foo,bar). 8 | %% ok 9 | %% 3> mydb:read(baz). 10 | %% {error, instance} 11 | %% 4> mydb:read(foo). 12 | %% {ok, bar} 13 | %% 5> mydb:match(bar). 14 | %% [foo] 15 | %% 7> mydb:stop(). 16 | %% ok 17 | %% 18 | -module(mydb). 19 | -export([start/0, stop/0]). 20 | -export([write/2, delete/1, read/1, match/1]). 21 | -export([init/1, terminate/2, handle_call/3, handle_cast/2]). 22 | -behaviour(gen_server). 23 | 24 | % Start/stop functions 25 | 26 | start() -> 27 | gen_server:start_link({local, ?MODULE}, ?MODULE, [], []). 28 | 29 | stop() -> 30 | gen_server:cast(?MODULE, stop). 31 | 32 | % Functional interface 33 | 34 | write(Key, Element) -> 35 | gen_server:call(?MODULE, {create, Key, Element}). 36 | 37 | delete(Key) -> 38 | gen_server:call(?MODULE, {delete, Key}). 39 | 40 | read(Key) -> 41 | gen_server:call(?MODULE, {read, Key}). 42 | 43 | match(Element) -> 44 | gen_server:call(?MODULE, {find_by_element, Element}). 45 | 46 | % Callback functions 47 | 48 | init(_) -> 49 | {ok, db:new()}. 50 | 51 | terminate(_Reason, Db) -> 52 | db:destroy(Db). 53 | 54 | handle_cast(stop, Db) -> 55 | {stop, normal, Db}. 56 | 57 | handle_call({create, Key, Element}, _From, Db) -> 58 | {reply, ok, db:write(Key, Element, Db)}; 59 | 60 | handle_call({delete, Key}, _From, Db) -> 61 | {reply, ok, db:delete(Key, Db)}; 62 | 63 | handle_call({read, Key}, _From, Db) -> 64 | {reply, db:read(Key, Db), Db}; 65 | 66 | handle_call({find_by_element, Element}, _From, Db) -> 67 | {reply, db:match(Element, Db), Db}. 68 | -------------------------------------------------------------------------------- /mydb/src/mydb_app.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.292. 3 | %% Exercise 12-3: Database Application 4 | %% 5 | %% 1> code:add_path("mydb/ebin"). 6 | %% true 7 | %% 2> application:start(mydb). 8 | %% ok 9 | %% 3> mydb:read(baz). 10 | %% {error,instance} 11 | %% 4> application:stop(mydb). 12 | %% ok 13 | %% 5> whereis(mydb_sup). 14 | %% undefined 15 | %% 16 | -module(mydb_app). 17 | -export([start/2, stop/1]). 18 | -behaviour(application). 19 | 20 | start(_Type, _StartArgs) -> 21 | mydb_sup:start(). 22 | 23 | stop(_State) -> 24 | ok. 25 | 26 | %% 1> application:start(mydb). 27 | %% ok 28 | %% 2> application:which_applications(). 29 | %% [{mydb,"In-memory Database","1.0"}, 30 | %% {stdlib,"ERTS CXC 138 10","1.16.3"}, 31 | %% {kernel,"ERTS CXC 138 10","2.13.3"}] 32 | %% 3> appmon:start(). 33 | %% {ok,<0.50.0>} 34 | -------------------------------------------------------------------------------- /mydb/src/mydb_sup.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.292. 3 | %% Exercise 12-2: Database Supervisor 4 | %% 5 | %% 1> mydb_sup:start(). 6 | %% {ok,<0.33.0>} 7 | %% 2> mydb:write(foo,bar). 8 | %% ok 9 | %% 3> mydb:match(bar). 10 | %% [foo] 11 | %% 12 | -module(mydb_sup). 13 | -export([start/0]). 14 | -export([init/1]). 15 | -behaviour(supervisor). 16 | 17 | start() -> 18 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 19 | 20 | init(_) -> 21 | MydbWorker = {mydb, {mydb, start, []}, permanent, 30000, worker, [mydb, db]}, 22 | {ok, {{one_for_all, 5, 3600}, [MydbWorker]}}. 23 | 24 | %% Killing supervisor kills worker 25 | %% 26 | %% 1> mydb_sup:start(). 27 | %% {ok,<0.48.0>} 28 | %% 2> whereis(mydb). 29 | %% <0.49.0> 30 | %% 3> exit(whereis(mydb_sup), kill). 31 | %% ** exception exit: killed 32 | %% 4> whereis(mydb). 33 | %% undefined 34 | 35 | %% Supervisor restarts killed worker 36 | %% 37 | %% 1> mydb_sup:start(). 38 | %% {ok,<0.70.0>} 39 | %% 2> whereis(mydb). 40 | %% <0.71.0> 41 | %% 3> exit(whereis(mydb), kill). 42 | %% true 43 | %% 4> whereis(mydb). 44 | %% <0.74.0> 45 | 46 | %% Supervisor cannot survive 6 worker deaths 47 | %% 48 | %% 1> mydb_sup:start(). 49 | %% {ok,<0.33.0>} 50 | %% 2> exit(whereis(mydb), kill). 51 | %% true 52 | %% 3> exit(whereis(mydb), kill). 53 | %% true 54 | %% 4> exit(whereis(mydb), kill). 55 | %% true 56 | %% 5> exit(whereis(mydb), kill). 57 | %% true 58 | %% 6> exit(whereis(mydb), kill). 59 | %% true 60 | %% 7> exit(whereis(mydb), kill). 61 | %% ** exception exit: shutdown 62 | %% 8> whereis(mydb_sup). 63 | %% undefined 64 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {erl_opts, [debug_info]}. 2 | {deps, []}. 3 | 4 | {eunit_opts, [verbose]}. 5 | 6 | {relx, [ 7 | {release, {ndpar_lib, "0.2.0"}, [ndpar_lib, sasl]}, {mode, dev}, 8 | {release, {barber, "0.2.0"}, [barber]}, {mode, dev}, 9 | {release, {examples, "0.1.0"}, [examples]}, {mode, dev}, 10 | {release, {mastermind, "1.0.0"}, [mastermind]}, {mode, dev} 11 | ]}. 12 | 13 | {profiles, [{prod, [{relx, 14 | [ 15 | %% prod is the default mode when prod 16 | %% profile is used, so does not have 17 | %% to be explicitly included like this 18 | {mode, prod} 19 | 20 | %% use minimal mode to exclude ERTS 21 | %% {mode, minimal} 22 | ] 23 | }]}]}. 24 | -------------------------------------------------------------------------------- /rebar.lock: -------------------------------------------------------------------------------- 1 | []. 2 | -------------------------------------------------------------------------------- /src/add_two.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.143. 3 | %% 4 | %% See also my_supervisor.erl 5 | %% 6 | -module(add_two). 7 | -export([start/0, request/1, loop/0]). 8 | 9 | start() -> 10 | process_flag(trap_exit, true), 11 | Pid = spawn_link(add_two, loop, []), 12 | register(add_two, Pid), 13 | {ok, Pid}. 14 | 15 | request(Int) -> 16 | add_two ! {request, self(), Int}, 17 | receive 18 | {result, Result} -> Result; 19 | {'EXIT', _Pid, Reason} -> {error, Reason} 20 | after 1000 -> timeout 21 | end. 22 | 23 | loop() -> 24 | receive 25 | {request, Pid, Msg} -> 26 | Pid ! {result, Msg + 2} 27 | end, 28 | loop(). 29 | -------------------------------------------------------------------------------- /src/bool.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.44. 3 | %% Exercise 2-3: Simple Pattern Matching 4 | %% 5 | -module(bool). 6 | -export([b_not/1, b_and/2, b_or/2, b_nand/2]). 7 | 8 | b_not(true) -> false; 9 | b_not(false) -> true. 10 | 11 | b_and(true,true) -> true; 12 | b_and(_,_) -> false. 13 | 14 | b_or(false,false) -> false; 15 | b_or(_,_) -> true. 16 | 17 | b_nand(X,Y) -> b_not(b_and(X,Y)). 18 | 19 | %% Solution from: http://groups.google.com/group/erlang-programming-book/browse_thread/thread/e95f323f72c72327# 20 | % b_and(true, true) -> true; 21 | % b_and(false, true) -> false; 22 | % b_and(true, false) -> false; 23 | % b_and(false, false) -> false. 24 | % b_or(true, true) -> true; 25 | % b_or(false, true) -> true; 26 | % b_or(true, false) -> true; 27 | % b_or(false, false) -> false. 28 | -------------------------------------------------------------------------------- /src/db2.erl: -------------------------------------------------------------------------------- 1 | %% Author: Andrey Paramonov 2 | %% Created: Nov 7, 2009 3 | %% Description: Simple DB (See "Erlang Programming", p.83) 4 | -module(db2). 5 | -export([new/0, destroy/1, write/3, read/2, match/2, delete/2]). 6 | 7 | %% 8 | %% Returns the list of keys for which Element is stored in the database 9 | %% match(Element, Db) -> lists:map(fun({Key,_}) -> Key end, (lists:filter(fun(X) -> {_,E} = X, E == Element end, Db))). 10 | %% 11 | match(Element, Db) -> [Key || {Key, E} <- Db, E == Element]. 12 | 13 | %% 14 | %% Reads Element with the specified Key from database Db 15 | %% 16 | read(Key, Db) -> 17 | Entry = lists:keyfind(Key, 1, Db), 18 | case Entry of 19 | false -> {error, instance}; 20 | {Key,Element} -> {ok, Element} 21 | end. 22 | 23 | %% 24 | %% Deletes Element with specified Key from datase Db 25 | %% 26 | delete(Key, Db) -> lists:keydelete(Key, 1, Db). 27 | 28 | %% 29 | %% Writes tuple {Key, Element} into database Db 30 | %% 31 | write(Key, Element, Db) -> lists:keystore(Key, 1, Db, {Key,Element}). 32 | 33 | %% 34 | %% Drops the database 35 | %% 36 | destroy(_Db) -> ok. 37 | 38 | %% 39 | %% Creates new database 40 | %% 41 | new() -> []. 42 | 43 | % Db = db2:new(). 44 | % Db1 = db2:write(francesco, london, Db). 45 | % Db2 = db2:write(lelle, stockholm, Db1). 46 | % db2:read(francesco, Db2). 47 | % Db3 = db2:write(joern, stockholm, Db2). 48 | % db2:read(ola, Db3). 49 | % db2:match(stockholm, Db3). 50 | % Db4 = db2:delete(lelle, Db3). 51 | % db2:match(stockholm, Db4). 52 | -------------------------------------------------------------------------------- /src/db3.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.169. 3 | % Exercise 7-3: Database of Records 4 | % 5 | -module(db3). 6 | -export([new/0, write/2, read/2, match/2, delete/2, destroy/1]). 7 | -include("Data.hrl"). 8 | 9 | write(Db, Data) -> 10 | #data{key = Key, value = Element} = Data, 11 | lists:keystore(Key, 1, Db, {Key, Element}). 12 | 13 | read(Db, Data) -> 14 | #data{key = Key} = Data, 15 | Entry = lists:keyfind(Key, 1, Db), 16 | case Entry of 17 | false -> {error, instance}; 18 | {Key, Element} -> Data#data{value = Element} 19 | end. 20 | 21 | match(Db, Data) -> 22 | #data{value = Element} = Data, 23 | [#data{key = Key, value = E} || {Key, E} <- Db, E == Element]. 24 | 25 | delete(Db, Data) -> 26 | #data{key = Key} = Data, 27 | lists:keydelete(Key, 1, Db), 28 | ok. 29 | 30 | destroy(_Db) -> ok. 31 | 32 | new() -> []. 33 | -------------------------------------------------------------------------------- /src/db3_server.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.169. 3 | % Exercise 7-3: Database of Records 4 | % 5 | -module(db3_server). 6 | -export([start/0, stop/0]). 7 | -export([write/1, delete/1, read/1, match/1]). 8 | -export([init/0]). 9 | 10 | % Start/stop functions 11 | 12 | start() -> 13 | register(db3_server, spawn(?MODULE, init, [])), ok. 14 | 15 | stop() -> 16 | db3_server ! stop, ok. 17 | 18 | init() -> 19 | loop(db3:new()). 20 | 21 | % Functional interface 22 | 23 | write(Data) -> 24 | call(create, Data). 25 | 26 | delete(Data) -> 27 | call(delete, Data). 28 | 29 | read(Data) -> 30 | call(read, Data). 31 | 32 | match(Data) -> 33 | call(find_by_element, Data). 34 | 35 | call(Command, Parameter) -> 36 | db3_server ! {request, self(), Command, Parameter}, 37 | receive {reply, Reply} -> Reply end. 38 | 39 | % Main loop 40 | 41 | loop(Db) -> 42 | receive 43 | stop -> 44 | db3:destroy(Db); 45 | {request, Pid, create, Data} -> 46 | NewDb = db3:write(Db, Data), 47 | reply(Pid, ok), 48 | loop(NewDb); 49 | {request, Pid, delete, Data} -> 50 | NewDb = db3:delete(Db, Data), 51 | reply(Pid, ok), 52 | loop(NewDb); 53 | {request, Pid, read, Data} -> 54 | Entry = db3:read(Db, Data), 55 | reply(Pid, Entry), 56 | loop(Db); 57 | {request, Pid, find_by_element, Data} -> 58 | Entries = db3:match(Db, Data), 59 | reply(Pid, Entries), 60 | loop(Db) 61 | end. 62 | 63 | reply(Pid, Msg) -> Pid ! {reply, Msg}. 64 | -------------------------------------------------------------------------------- /src/db3_server_test.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.169. 3 | % Exercise 7-3: Database of Records 4 | % 5 | -module(db3_server_test). 6 | -export([test1/0]). 7 | -include("Data.hrl"). 8 | 9 | % 1> db3_server_test:test1(). 10 | % write(FooBar): ok 11 | % read(Baz): {error,instance} 12 | % read(Foo): {data,foo,bar} 13 | % match(Bar): [{data,foo,bar}] 14 | % ok 15 | % 16 | test1() -> 17 | db3_server:start(), 18 | 19 | FooBar = #data{key = foo, value = bar}, 20 | io:format("write(FooBar): ~p~n", [db3_server:write(FooBar)]), 21 | 22 | Baz = #data{key = baz}, 23 | io:format("read(Baz): ~p~n", [db3_server:read(Baz)]), 24 | 25 | Foo = #data{key = foo}, 26 | io:format("read(Foo): ~p~n", [db3_server:read(Foo)]), 27 | 28 | Bar = #data{value = bar}, 29 | io:format("match(Bar): ~p~n", [db3_server:match(Bar)]), 30 | 31 | db3_server:stop(). 32 | -------------------------------------------------------------------------------- /src/echo.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% In the shell type: echo:go(). 3 | %% 4 | -module(echo). 5 | -export([go/0, loop/0]). 6 | 7 | go() -> 8 | io:format("~w Hi~n", [self()]), 9 | 10 | Pid = spawn(echo, loop, []), 11 | io:format("~w Spawned process ~w~n", [self(), Pid]), 12 | 13 | Pid ! {self(), hello}, 14 | io:format("~w Sent hallo to ~w~n", [self(), Pid]), 15 | 16 | io:format("~w Start listening for incoming messages~n", [self()]), 17 | receive 18 | {Pid, Msg} -> 19 | io:format("~w Received ~w from ~w~n", [self(), Msg, Pid]) 20 | end, 21 | Pid ! stop, 22 | io:format("~w Sent stop~n", [self()]). 23 | 24 | loop() -> 25 | io:format("~w Hi~n", [self()]), 26 | io:format("~w Start listening for incoming messages~n", [self()]), 27 | receive 28 | {From, Msg} -> 29 | io:format("~w Received ~w from ~w~n", [self(), Msg, From]), 30 | From ! {self(), Msg}, 31 | io:format("~w Sent ~w back to ~w~n", [self(), Msg, From]), 32 | loop(); 33 | stop -> 34 | io:format("~w Received stop~n", [self()]), 35 | true 36 | end. -------------------------------------------------------------------------------- /src/event_manager.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.133. 3 | %% Event Manager Pattern. 4 | %% 5 | %% 1> event_manager:start(alarm, [{log_handler, "alarm.log"}]). 6 | %% 2> event_manager:send_event(alarm, {raise_alarm, 10, cabinet_open}). 7 | %% 3> event_manager:add_handler(alarm, io_handler, 1). 8 | %% 4> event_manager:send_event(alarm, {clear_alarm, 10, cabinet_open}). 9 | %% 5> event_manager:send_event(alarm, {event, 156, link_up}). 10 | %% 6> event_manager:get_data(alarm, io_handler). 11 | %% 7> event_manager:delete_handler(alarm, stats_handler). 12 | %% 8> event_manager:stop(alarm). 13 | %% 14 | %% See also io_handler.erl, log_handler.erl 15 | %% 16 | -module(event_manager). 17 | -export([start/2, stop/1]). 18 | -export([add_handler/3, delete_handler/2, get_data/2, send_event/2]). 19 | -export([swap_handlers/3]). 20 | -export([init/1]). 21 | 22 | % Start/stop functions 23 | 24 | start(Name, HandlerList) -> 25 | register(Name, spawn(?MODULE, init, [HandlerList])), ok. 26 | 27 | stop(Name) -> 28 | Name ! {stop, self()}, 29 | receive {reply, Reply} -> Reply end. 30 | 31 | init(HandlerList) -> 32 | loop(initialize(HandlerList)). 33 | 34 | initialize([]) -> []; 35 | initialize([{Handler, InitData}|Rest]) -> 36 | [{Handler, Handler:init(InitData)}|initialize(Rest)]. 37 | 38 | % Interface 39 | 40 | add_handler(Name, Handler, InitData) -> 41 | call(Name, {add_handler, Handler, InitData}). 42 | 43 | delete_handler(Name, Handler) -> 44 | call(Name, {delete_handler, Handler}). 45 | 46 | get_data(Name, Handler) -> 47 | call(Name, {get_data, Handler}). 48 | 49 | send_event(Name, Event) -> 50 | call(Name, {send_event, Event}). 51 | 52 | % Main loop 53 | 54 | call(Name, Msg) -> 55 | Name ! {request, self(), Msg}, 56 | receive {reply, Reply} -> Reply end. 57 | 58 | loop(State) -> 59 | receive 60 | {request, From, Msg} -> 61 | {Reply, NewState} = handle_msg(Msg, State), 62 | reply(From, Reply), 63 | loop(NewState); 64 | {stop, From} -> 65 | reply(From, terminate(State)) 66 | end. 67 | 68 | reply(To, Msg) -> 69 | To ! {reply, Msg}. 70 | 71 | handle_msg({add_handler, Handler, InitData}, State) -> 72 | case lists:keysearch(Handler, 1, State) of 73 | false -> 74 | {ok, [{Handler, Handler:init(InitData)}|State]}; 75 | _ -> 76 | {{error, already_registered}, State} 77 | end; 78 | 79 | handle_msg({delete_handler, Handler}, State) -> 80 | case lists:keysearch(Handler, 1, State) of 81 | false -> 82 | {{error, instance}, State}; 83 | {value, {Handler, Data}} -> 84 | Reply = {data, Handler:terminate(Data)}, 85 | NewState = lists:keydelete(Handler, 1, State), 86 | {Reply, NewState} 87 | end; 88 | 89 | %% I'm not sure how it's supposed to work 90 | %% so I change the type of NewHandler to file name 91 | %% (see the next method) 92 | %% 93 | %handle_msg({swap_handlers, OldHandler, NewHandler}, State) -> 94 | % case lists:keysearch(OldHandler, 1, State) of 95 | % false -> 96 | % {{error, instance}, State}; 97 | % {value, {OldHandler, Data}} -> 98 | % NewInitData = OldHandler:terminate(Data), 99 | % NewData = NewHandler:init(NewInitData), 100 | % Reply = {data, NewData}, 101 | % NewState = lists:keyreplace(OldHandler, 1, State, {NewHandler, NewData}), 102 | % {Reply, NewState} 103 | % end; 104 | 105 | handle_msg({swap_handlers, log_handler, NewFileName}, State) -> 106 | case lists:keysearch(log_handler, 1, State) of 107 | false -> 108 | {{error, instance}, State}; 109 | {value, {log_handler, Fd}} -> 110 | log_handler:terminate(Fd), 111 | NewFd = log_handler:init(NewFileName), 112 | Reply = {data, NewFd}, 113 | NewState = lists:keyreplace(log_handler, 1, State, {log_handler, NewFd}), 114 | {Reply, NewState} 115 | end; 116 | 117 | handle_msg({get_data, Handler}, State) -> 118 | case lists:keysearch(Handler, 1, State) of 119 | false -> 120 | {{error, instance}, State}; 121 | {value, {Handler, Data}} -> 122 | {{data, Data}, State} 123 | end; 124 | 125 | handle_msg({send_event, Event}, State) -> 126 | {ok, event(Event, State)}. 127 | 128 | event(_Event, []) -> []; 129 | event(Event, [{Handler, Data}|Rest]) -> 130 | [{Handler, Handler:handle_event(Event, Data)}|event(Event, Rest)]. 131 | 132 | terminate([]) -> []; 133 | terminate([{Handler, Data}|Rest]) -> 134 | [{Handler, Handler:terminate(Data)}|terminate(Rest)]. 135 | 136 | % Event Handler interface: 137 | % 138 | % init(InitData) 139 | % terminate(Data) 140 | % handle_event(Event, Data) 141 | 142 | 143 | %% Exercise 5-3: Swapping Handlers 144 | %% 145 | %% 1> event_manager:start(alarm, [{log_handler, "alarm.log"}]). 146 | %% 2> event_manager:send_event(alarm, {event, 156, go_to_alarm}). 147 | %% 3> event_manager:swap_handlers(alarm, log_handler, "server.log"). 148 | %% 4> event_manager:send_event(alarm, {event, 166, go_to_server}). 149 | %% 5> event_manager:stop(alarm). 150 | 151 | swap_handlers(Name, OldHandler, NewHandler) -> 152 | call(Name, {swap_handlers, OldHandler, NewHandler}). 153 | -------------------------------------------------------------------------------- /src/factorial.erl: -------------------------------------------------------------------------------- 1 | % 2 | % 1> factorial:start(). 3 | % 2> factorial:next(). 4 | % 3> factorial:next(). 5 | % ... 6 | % 7 | -module(factorial). 8 | -export([start/0, next/0]). 9 | -export([init/0]). 10 | 11 | start() -> 12 | register(factorial, spawn(?MODULE, init, [])). 13 | 14 | next() -> 15 | factorial ! {next, self()}, 16 | receive N -> N end. 17 | 18 | init() -> 19 | loop(next(1, 1)). 20 | 21 | next(Acc, N) -> 22 | fun() -> [Acc * N | next(Acc * N, N + 1)] end. 23 | 24 | loop(Fun) -> 25 | receive 26 | {next, From} -> 27 | [N | NewFun] = Fun(), 28 | From ! N, 29 | loop(NewFun) 30 | end. 31 | -------------------------------------------------------------------------------- /src/frequency.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.121. 3 | %% Client/Server Pattern. 4 | %% 5 | %% 1> frequency:start(). 6 | %% 2> frequency:allocate(). 7 | %% ... 8 | %% 8> frequency:allocate(). 9 | %% 9> frequency:deallocate(11). 10 | %% 10> frequency:allocate(). 11 | %% 11> frequency:stop(). 12 | %% 13 | -module(frequency). 14 | -export([start/0, stop/0]). 15 | -export([allocate/0, deallocate/1]). 16 | -export([init/0]). 17 | 18 | % Start function to create and initialize the server 19 | 20 | start() -> 21 | register(frequency, spawn(frequency, init, [])). 22 | 23 | init() -> 24 | Frequencies = {get_frequencies(), []}, 25 | loop(Frequencies). 26 | 27 | get_frequencies() -> [10,11,12,13,14,15]. 28 | 29 | loop(Frequencies) -> 30 | receive 31 | {request, Pid, allocate} -> 32 | {NewFrequencies, Reply} = allocate(Frequencies, Pid), 33 | reply(Pid, Reply), 34 | loop(NewFrequencies); 35 | {request, Pid, {deallocate, Freq}} -> 36 | NewFrequencies = deallocate(Frequencies, Freq), 37 | reply(Pid, ok), 38 | loop(NewFrequencies); 39 | {request, Pid, stop} -> 40 | reply(Pid, ok) 41 | end. 42 | 43 | allocate({[], Allocated}, _Pid) -> 44 | {{[], Allocated}, {error, no_frequency}}; 45 | allocate({[Freq|Free], Allocated}, Pid) -> 46 | {{Free, [{Freq,Pid}|Allocated]}, {ok, Freq}}. 47 | 48 | deallocate({Free,Allocated}, Freq) -> 49 | NewAllocated = lists:keydelete(Freq, 1, Allocated), 50 | {[Freq|Free], NewAllocated}. 51 | 52 | reply(Pid, Reply) -> 53 | Pid ! {reply, Reply}. 54 | 55 | 56 | % Client functions 57 | 58 | stop() -> call(stop). 59 | allocate() -> call(allocate). 60 | deallocate(Freq) -> call({deallocate, Freq}). 61 | 62 | call(Message) -> 63 | frequency ! {request, self(), Message}, 64 | receive 65 | {reply, Reply} -> Reply 66 | end. -------------------------------------------------------------------------------- /src/frequency2.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.138. 3 | %% Exercise 5-2 4 | %% 5 | %% 1> frequency2:start(). 6 | %% ok 7 | %% 2> frequency2:allocate(). 8 | %% {ok,10} 9 | %% 3> frequency2:allocate(). 10 | %% {ok,11} 11 | %% 4> frequency2:allocate(). 12 | %% {ok,12} 13 | %% 5> frequency2:allocate(). 14 | %% {error,exceed_limit} 15 | %% 6> frequency2:deallocate(13). 16 | %% {error,foreign_frequency} 17 | %% 7> frequency2:deallocate(11). 18 | %% ok 19 | %% 8> frequency2:stop(). 20 | %% {error,frequencies_in_use} 21 | %% 9> frequency2:deallocate(10). 22 | %% ok 23 | %% 10> frequency2:deallocate(12). 24 | %% ok 25 | %% 11> frequency2:stop(). 26 | %% ok 27 | %% 28 | -module(frequency2). 29 | -export([start/0, stop/0]). 30 | -export([allocate/0, deallocate/1]). 31 | -export([init/0]). 32 | 33 | % Start function to create and initialize the server 34 | 35 | start() -> 36 | register(frequency2, spawn(frequency2, init, [])), ok. 37 | 38 | init() -> 39 | Frequencies = {get_frequencies(), []}, 40 | loop(Frequencies). 41 | 42 | get_frequencies() -> [10,11,12,13,14,15]. 43 | 44 | loop(Frequencies) -> 45 | receive 46 | {request, Pid, allocate} -> 47 | {NewFrequencies, Reply} = allocate(Frequencies, Pid), 48 | reply(Pid, Reply), 49 | loop(NewFrequencies); 50 | {request, Pid, {deallocate, Freq}} -> 51 | {NewFrequencies, Reply} = deallocate(Frequencies, Freq, Pid), 52 | reply(Pid, Reply), 53 | loop(NewFrequencies); 54 | {request, Pid, stop} -> 55 | {_, Allocated} = Frequencies, 56 | case Allocated of 57 | [] -> 58 | reply(Pid, ok); 59 | _ -> 60 | reply(Pid, {error, frequencies_in_use}), 61 | loop(Frequencies) 62 | end 63 | end. 64 | 65 | allocate({[], Allocated}, _Pid) -> 66 | {{[], Allocated}, {error, no_frequency}}; 67 | 68 | allocate({[Freq|Free], Allocated}, Pid) -> 69 | ClientFrequencies = my_lists:keyfilter(Pid, 2, Allocated), 70 | case length(ClientFrequencies) of 71 | 3 -> {{[Freq|Free], Allocated}, {error, exceed_limit}}; 72 | _ -> {{Free, [{Freq,Pid}|Allocated]}, {ok, Freq}} 73 | end. 74 | 75 | deallocate({Free, Allocated}, Freq, Pid) -> 76 | case lists:member({Freq, Pid}, Allocated) of 77 | true -> 78 | NewAllocated = lists:keydelete(Freq, 1, Allocated), 79 | {{[Freq|Free], NewAllocated}, ok}; 80 | _ -> 81 | {{Free, Allocated}, {error, foreign_frequency}} 82 | end. 83 | 84 | reply(Pid, Reply) -> 85 | Pid ! {reply, Reply}. 86 | 87 | % Client functions 88 | 89 | stop() -> call(stop). 90 | allocate() -> call(allocate). 91 | deallocate(Freq) -> call({deallocate, Freq}). 92 | 93 | call(Message) -> 94 | frequency2 ! {request, self(), Message}, 95 | receive {reply, Reply} -> Reply end. 96 | -------------------------------------------------------------------------------- /src/frequency_reliable.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.150. 3 | %% Reliable Client/Server 4 | %% 5 | %% Based on frequency2.erl 6 | %% 7 | %% Test 1: Kill the client 8 | %% 9 | %% 1> frequency_reliable:start(). 10 | %% ok 11 | %% 2> frequency_reliable:allocate(). 12 | %% {ok,10} 13 | %% 3> frequency_reliable:allocate(). 14 | %% {ok,11} 15 | %% 4> exit(self(), kill). 16 | %% ** exception exit: killed 17 | %% 5> frequency_reliable:allocate(). 18 | %% {ok,10} 19 | %% 6> frequency_reliable:allocate(). 20 | %% {ok,11} 21 | %% 22 | %% Test 2: Kill the server 23 | %% 24 | %% 1> frequency_reliable:start(). 25 | %% ok 26 | %% 2> frequency_reliable:allocate(). 27 | %% {ok,10} 28 | %% 3> self(). 29 | %% <0.31.0> 30 | %% 4> exit(whereis(frequency_reliable), kill). 31 | %% true 32 | %% 5> self(). 33 | %% <0.37.0> 34 | %% 35 | -module(frequency_reliable). 36 | -export([start/0, stop/0]). 37 | -export([allocate/0, deallocate/1]). 38 | -export([init/0]). 39 | 40 | % Start function to create and initialize the server 41 | 42 | start() -> 43 | register(frequency_reliable, spawn(frequency_reliable, init, [])), ok. 44 | 45 | init() -> 46 | process_flag(trap_exit, true), 47 | Frequencies = {get_frequencies(), []}, 48 | loop(Frequencies). 49 | 50 | get_frequencies() -> [10,11,12,13,14,15]. 51 | 52 | loop(Frequencies) -> 53 | receive 54 | {request, Pid, allocate} -> 55 | {NewFrequencies, Reply} = allocate(Frequencies, Pid), 56 | reply(Pid, Reply), 57 | loop(NewFrequencies); 58 | {request, Pid, {deallocate, Freq}} -> 59 | {NewFrequencies, Reply} = deallocate(Frequencies, Freq, Pid), 60 | reply(Pid, Reply), 61 | loop(NewFrequencies); 62 | {request, Pid, stop} -> 63 | {_, Allocated} = Frequencies, 64 | case Allocated of 65 | [] -> 66 | reply(Pid, ok); 67 | _ -> 68 | reply(Pid, {error, frequencies_in_use}), 69 | loop(Frequencies) 70 | end; 71 | {'EXIT', Pid, _Reason} -> 72 | NewFrequencies = exited(Frequencies, Pid), 73 | loop(NewFrequencies) 74 | end. 75 | 76 | exited({Free, Allocated}, Pid) -> 77 | case lists:keysearch(Pid, 2, Allocated) of 78 | {value, {Freq, Pid}} -> 79 | NewAllocated = lists:keydelete(Freq, 1, Allocated), 80 | exited({[Freq|Free], NewAllocated}, Pid); 81 | false -> 82 | {Free, Allocated} 83 | end. 84 | 85 | allocate({[], Allocated}, _Pid) -> 86 | {{[], Allocated}, {error, no_frequency}}; 87 | 88 | allocate({[Freq|Free], Allocated}, Pid) -> 89 | ClientFrequencies = my_lists:keyfilter(Pid, 2, Allocated), 90 | case length(ClientFrequencies) of 91 | 3 -> {{[Freq|Free], Allocated}, {error, exceed_limit}}; 92 | _ -> 93 | link(Pid), 94 | {{Free, [{Freq,Pid}|Allocated]}, {ok, Freq}} 95 | end. 96 | 97 | deallocate({Free, Allocated}, Freq, Pid) -> 98 | case lists:member({Freq, Pid}, Allocated) of 99 | true -> 100 | unlink(Pid), 101 | NewAllocated = lists:keydelete(Freq, 1, Allocated), 102 | {{[Freq|Free], NewAllocated}, ok}; 103 | _ -> 104 | {{Free, Allocated}, {error, foreign_frequency}} 105 | end. 106 | 107 | reply(Pid, Reply) -> 108 | Pid ! {reply, Reply}. 109 | 110 | % Client functions 111 | 112 | stop() -> call(stop). 113 | allocate() -> call(allocate). 114 | deallocate(Freq) -> call({deallocate, Freq}). 115 | 116 | call(Message) -> 117 | frequency_reliable ! {request, self(), Message}, 118 | receive {reply, Reply} -> Reply end. 119 | -------------------------------------------------------------------------------- /src/generic_server.erl: -------------------------------------------------------------------------------- 1 | %% How to use: 2 | %% 3 | %% generic_server:start(). 4 | %% generic_server:request(5). => 7 5 | %% 6 | %% generic_server:change_state(10). 7 | %% generic_server:request(5). => 15 8 | %% 9 | %% generic_server:change_function( fun(X,Y) -> X * Y end ). 10 | %% generic_server:request(5). => 50 11 | 12 | -module(generic_server). 13 | 14 | -export([start/0, loop/2]). 15 | -export([request/1, change_state/1, change_function/1]). 16 | 17 | %% Server 18 | 19 | start() -> 20 | register(?MODULE, spawn(?MODULE, loop, [2, fun erlang:'+'/2])). 21 | 22 | loop(State, Function) -> 23 | receive 24 | {new_state, S} -> loop(S, Function); 25 | {new_fun, F} -> loop(State, F); 26 | {request, From, Request} -> 27 | From ! {response, apply(Function, [State, Request])}, 28 | loop(State, Function) 29 | end. 30 | 31 | %% Client 32 | 33 | change_state(NewState) -> 34 | ?MODULE ! {new_state, NewState}, 35 | ok. 36 | 37 | change_function(NewFunction) -> 38 | ?MODULE ! {new_fun, NewFunction}, 39 | ok. 40 | 41 | request(Request) -> 42 | ?MODULE ! {request, self(), Request}, 43 | receive 44 | {response, Result} -> Result 45 | end. 46 | 47 | -------------------------------------------------------------------------------- /src/hof.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.211. 3 | % Exercise 9-1: Higher Order Functions 4 | % 5 | -module(hof). 6 | -export([print_integers/2]). 7 | 8 | print_integers(From, To) -> 9 | lists:foreach(fun(X) -> io:format("~p~n", [X]) end, lists:seq(From, To)). 10 | 11 | squares(List) -> [X*X || X <- List, is_integer(X)]. 12 | 13 | intersection(L1, L2) -> [X || X <- L1, Y <- L2, X =:= Y]. 14 | 15 | diff(L1, L2) -> [X || X <- L1, not lists:member(X, L2)]. 16 | sym_diff(L1, L2) -> diff(L1, L2) ++ diff(L2, L1). 17 | 18 | zip(L1, L2) -> zip([], L1, L2). 19 | zip(Acc, _, []) -> Acc; 20 | zip(Acc, [], _) -> Acc; 21 | zip(Acc, [X|Xs], [Y|Ys]) -> zip(Acc ++ [{X,Y}], Xs, Ys). 22 | 23 | zipwith(Fun, L1, L2) -> lists:map(Fun, zip(L1, L2)). 24 | 25 | 26 | -include_lib("eunit/include/eunit.hrl"). 27 | 28 | squares_test() -> 29 | ?assertEqual([1,10000,81], squares([1,hello,100,boo,"boo",9])). 30 | 31 | intersection_test() -> 32 | ?assertEqual([4,5], intersection([1,2,3,4,5], [4,5,6,7,8])). 33 | 34 | sym_diff_test() -> 35 | ?assertEqual([1,2,3,6,7,8], sym_diff([1,2,3,4,5], [4,5,6,7,8])). 36 | 37 | zip_fails_on_lists_with_different_length_test() -> 38 | ?assertError(function_clause, lists:zip([1,2],[3,4,5])). 39 | 40 | zip_left_list_test() -> 41 | ?assertEqual([{1,3},{2,4}], zip([1,2],[3,4,5])). 42 | 43 | zip_right_list_test() -> 44 | ?assertEqual([{1,4},{2,5}], zip([1,2,3],[4,5])). 45 | 46 | zipwith_test() -> 47 | Add = fun({X,Y}) -> X + Y end, 48 | ?assertEqual([4,6], zipwith(Add, [1,2],[3,4,5])). 49 | -------------------------------------------------------------------------------- /src/id_generator.erl: -------------------------------------------------------------------------------- 1 | % Copyright (c) 2009, Andrey Paramonov 2 | % All rights reserved. 3 | % 4 | -module(id_generator). 5 | -export([start/0, next/0, stop/0]). 6 | -export([loop/1]). 7 | 8 | start() -> 9 | register(id_generator, spawn(id_generator, loop, [0])), ok. 10 | 11 | next() -> 12 | id_generator ! {next, self()}, 13 | receive {reply, Result} -> Result end. 14 | 15 | stop() -> 16 | id_generator ! {stop, self()}, 17 | receive {reply, stopped} -> ok end. 18 | 19 | loop(Count) -> 20 | receive 21 | {next, From} -> 22 | From ! {reply, Count}, 23 | loop(Count + 1); 24 | {stop, From} -> 25 | From ! {reply, stopped} 26 | end. 27 | -------------------------------------------------------------------------------- /src/io_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.136. 3 | %% Event Manager Pattern. 4 | %% 5 | %% See also event_manager.erl 6 | %% 7 | -module(io_handler). 8 | -export([init/1, terminate/1, handle_event/2]). 9 | 10 | init(Count) -> Count. 11 | 12 | terminate(Count) -> {count, Count}. 13 | 14 | handle_event({raise_alarm, Id, Alarm}, Count) -> 15 | print(alarm, Id, Alarm, Count), 16 | Count + 1; 17 | handle_event({clear_alarm, Id, Alarm}, Count) -> 18 | print(clear, Id, Alarm, Count), 19 | Count + 1; 20 | handle_event(_Event, Count) -> 21 | Count. 22 | 23 | print(Type, Id, Alarm, Count) -> 24 | Date = fmt(date()), Time = fmt(time()), 25 | io:format("#~w,~s,~s,~w,~w,~p~n", [Count, Date, Time, Type, Id, Alarm]). 26 | 27 | fmt({AInt, BInt, CInt}) -> 28 | AStr = pad(integer_to_list(AInt)), 29 | BStr = pad(integer_to_list(BInt)), 30 | CStr = pad(integer_to_list(CInt)), 31 | [AStr,$:,BStr,$:,CStr]. 32 | 33 | pad([M1]) -> [$0,M1]; 34 | pad(Other) -> Other. -------------------------------------------------------------------------------- /src/life.erl: -------------------------------------------------------------------------------- 1 | -module(life). 2 | -export([neighbours/1, next_step/1]). 3 | 4 | neighbours({X, Y}) -> 5 | [{X + DX, Y + DY} || DX <- [-1, 0, 1], DY <- [-1, 0, 1], {DX, DY} =/= {0, 0}]. 6 | 7 | next_step(Cells) -> 8 | Nbs = lists:flatmap(fun neighbours/1, sets:to_list(Cells)), 9 | NewCells = [C || {C, N} <- maps:to_list(core:frequencies(Nbs)), 10 | (N == 3) orelse ((N == 2) andalso sets:is_element(C, Cells))], 11 | sets:from_list(NewCells). 12 | 13 | % 14 | % Unit tests 15 | % 16 | 17 | -include_lib("eunit/include/eunit.hrl"). 18 | 19 | neighbours_test() -> 20 | ?assertEqual([{0,1}, {0,2}, {0,3}, {1,1}, {1,3}, {2,1}, {2,2}, {2,3}], 21 | neighbours({1, 2})). 22 | 23 | blinker_test() -> 24 | assert_next_step([{2,3}, {3,3}, {4,3}], [{3,2}, {3,3}, {3,4}]), 25 | assert_next_step([{3,2}, {3,3}, {3,4}], [{2,3}, {3,3}, {4,3}]). 26 | 27 | beehive_test() -> 28 | assert_next_step([{3,2}, {2,3}, {2,4}, {3,5}, {4,4}, {4,3}], 29 | [{3,2}, {2,3}, {2,4}, {3,5}, {4,4}, {4,3}]). 30 | 31 | assert_next_step(ListAfter, ListBefore) -> 32 | ?assertEqual(sets:from_list(ListAfter), next_step(sets:from_list(ListBefore))). 33 | -------------------------------------------------------------------------------- /src/life_async.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Asynchronous Game of Life 3 | %%% http://en.wikipedia.org/wiki/Conway's_Game_of_Life 4 | %%% 5 | %%% This implementation preserves the standard game behaviour by 6 | %%% introducing a master process which represents a global clock. 7 | %%% 8 | %%% The game runs for finite number of steps and then stops. 9 | %%% 10 | %%% Another feature of this implementation is that new processes 11 | %%% are spawned for live cells only, therefore it requires less 12 | %%% processes than fully async implementation. 13 | %%% 14 | %%% See also: 15 | %%% life.erl - standard game implementation, 16 | %%% life_async_grid - fully async implementation. 17 | %%% 18 | -module(life_async). 19 | -author("Andrey Paramonov "). 20 | 21 | %% Published API 22 | -export([start/2]). 23 | 24 | %% Internal functions for process spawning 25 | -export([new_cell/3]). 26 | 27 | %% ------------------------------------------------------------------ 28 | %% Game Logic 29 | %% ------------------------------------------------------------------ 30 | 31 | %% @doc Starts the new game and runs Iter iterations. 32 | %% Seed is a list of unique cell coordinates. 33 | %% 34 | %% Blinker example: life_async:start([{3,2},{3,3},{3,4}], 2). 35 | %% 36 | %% Returns final generation. 37 | start(Seed, Iter) -> 38 | god(Seed, new_world(), Iter). 39 | 40 | %% ------------------------------------------------------------------ 41 | %% Master process 42 | %% ------------------------------------------------------------------ 43 | 44 | %% @doc Main loop (usually runs in shell process). 45 | %% Map is a list of coordinates where new cells to be born. 46 | %% World is a hashmap of live cells. Keys are coordinates, 47 | %% values are processes. One process per live cell. 48 | god(Map, World, 0) -> 49 | ok = apocalypse(cells(World)), 50 | Map ++ coordinates(World); 51 | god(Map, World, N) -> 52 | io:format("~p: ~p~n", [N, Map ++ coordinates(World)]), 53 | NewWorld = create(Map, World), 54 | ok = leave_them_alone(Map), 55 | NewMap = schedule_new_cells(NewWorld), 56 | god(NewMap, step(NewWorld), N-1). 57 | 58 | %% @doc When game is over, all cells die. 59 | apocalypse([]) -> ok; 60 | apocalypse([Cell|Cells]) -> 61 | exit(Cell, kill), 62 | apocalypse(Cells). 63 | 64 | %% @doc Make every new cell alive and add it to the World. 65 | create([], World) -> World; 66 | create([Coord|Cs], World) -> 67 | Cell = spawn(?MODULE, new_cell, [Coord, World, self()]), 68 | create(Cs, add(Coord, Cell, World)). 69 | 70 | %% @doc Wait for every cell to confirm it is doing ok. 71 | leave_them_alone([]) -> ok; 72 | leave_them_alone([C|Cs]) -> 73 | receive 74 | {created, C} -> leave_them_alone(Cs) 75 | end. 76 | 77 | %% @doc It is God's responsibility to decide which cell becomes 78 | %% alive on the next step. 79 | schedule_new_cells(World) -> 80 | Cells = cells(World), 81 | ok = ask_for_dead_neighbours(Cells), 82 | Counters = collect_dead_neighbours_info(Cells), 83 | new_map(Counters). 84 | 85 | %% @doc Ask every cell to check which of its neighbours is dead. 86 | ask_for_dead_neighbours([]) -> ok; 87 | ask_for_dead_neighbours([Cell|Cells]) -> 88 | Cell ! {dead_neighbours, self()}, 89 | ask_for_dead_neighbours(Cells). 90 | 91 | %% @doc Wait for all cells to report about their dead neighbours. 92 | %% Return a dictionary of frequencies, i.e. keys are coordinates 93 | %% of dead cells, values are numbers of reports. 94 | collect_dead_neighbours_info(Cells) -> 95 | lists:foldl( 96 | fun(Cell, Acc) -> 97 | receive 98 | {dead_cells, Cell, DeadCells} -> 99 | update_counters(DeadCells, Acc) 100 | end 101 | end, 102 | dict:new(), Cells). 103 | 104 | update_counters([], Counters) -> Counters; 105 | update_counters([C|Cs], Counters) -> 106 | update_counters(Cs, dict:update_counter(C, 1, Counters)). 107 | 108 | %% @doc Select which cells will be alive on the next step. 109 | %% If 3 live cells reported about the same dead cell, 110 | %% it will become alive. 111 | new_map(Counters) -> new_map(dict:to_list(Counters), []). 112 | new_map([], Acc) -> Acc; 113 | new_map([{C,3}|Cs], Acc) -> new_map(Cs, [C|Acc]); 114 | new_map([_|Cs], Acc) -> new_map(Cs, Acc). 115 | 116 | %% @doc Runs one step of the game. 117 | %% Let the live cells to choose their future, then collect survivals. 118 | step(World) -> 119 | Cells = cells(World), 120 | ok = time_to_choose(Cells), 121 | filter_survivals(Cells, World). 122 | 123 | time_to_choose([]) -> ok; 124 | time_to_choose([Cell|Cells]) -> 125 | Cell ! {live_or_die, self()}, 126 | time_to_choose(Cells). 127 | 128 | filter_survivals(Cells, World) -> 129 | lists:foldl( 130 | fun(Cell, Acc) -> 131 | receive 132 | {dying, Cell, Coord} -> remove(Coord, Acc); 133 | {survived, Cell, _} -> Acc 134 | end 135 | end, 136 | World, Cells). 137 | 138 | %% ------------------------------------------------------------------ 139 | %% Cell process 140 | %% ------------------------------------------------------------------ 141 | 142 | %% @doc Initial steps of newly born cell. 143 | new_cell(Coord, World, God) -> 144 | Cells = cells(World), 145 | ok = hello_world(Cells, Coord), 146 | Neighbours = know_your_neighbours(Cells, Coord), 147 | ok = mature(God, Coord), 148 | cell(Coord, Neighbours). 149 | 150 | %% @doc Tell everyone about your existence. 151 | hello_world([], _) -> ok; 152 | hello_world([Cell|Cells], Coord) -> 153 | Cell ! {ping, self(), Coord}, 154 | hello_world(Cells, Coord). 155 | 156 | %% @doc Wait for everyone to respond to see who is your neighbour. 157 | know_your_neighbours(Cells, Coord) -> 158 | lists:foldl( 159 | fun(Cell, Acc) -> 160 | receive 161 | {pong, Cell, NCoord} -> 162 | case is_neighbour(Coord, NCoord) of 163 | true -> add(NCoord, Cell, Acc); 164 | _ -> Acc 165 | end 166 | end 167 | end, 168 | new_world(), Cells). 169 | 170 | is_neighbour({X1,Y1}, {X2,Y2}) -> 171 | Dx = abs(X1 - X2), 172 | Dy = abs(Y1 - Y2), 173 | Dx < 2 andalso Dy < 2. 174 | 175 | %% @doc Master process is waiting for your maturity. 176 | %% Send a message to unblock it. 177 | mature(God, Coord) -> 178 | God ! {created, Coord}, 179 | ok. 180 | 181 | %% @doc Main loop of live cell is to respond to messages 182 | %% from other cells and from master process. 183 | cell(Coord, Neighbours) -> 184 | Cells = cells(Neighbours), 185 | receive 186 | {ping, Cell, NCoord} -> 187 | % Tell about yourself 188 | Cell ! {pong, self(), Coord}, 189 | % and check if newcomer is your new neighbour 190 | case is_neighbour(Coord, NCoord) of 191 | true -> cell(Coord, add(NCoord, Cell, Neighbours)); 192 | _ -> cell(Coord, Neighbours) 193 | end; 194 | {live_or_die, God} -> 195 | % To be or not to be - it is actually your choice 196 | NewState = case population(Neighbours) of 197 | 2 -> survived; 198 | 3 -> survived; 199 | _ -> dying 200 | end, 201 | % Tell your neighbours about your choice 202 | lists:foreach(fun(N) -> N ! {NewState, self(), Coord} end, Cells), 203 | % Check which of your neighbours is going to die 204 | NewNeighbours = lists:foldl( 205 | fun(Cell, Acc) -> 206 | receive 207 | {dying, _, _} -> Acc; 208 | {survived, Cell, NCoord} -> add(NCoord, Cell, Acc) 209 | end 210 | end, 211 | new_world(), Cells), 212 | % Notify master process about your choice 213 | God ! {NewState, self(), Coord}, 214 | case NewState of 215 | survived -> cell(Coord, NewNeighbours); 216 | dying -> ok 217 | end; 218 | {dead_neighbours, God} -> 219 | % help master process to conduct a census 220 | God ! {dead_cells, self(), dead_cells(Coord, Neighbours)}, 221 | cell(Coord, Neighbours) 222 | end. 223 | 224 | dead_cells(Coord, Neighbours) -> 225 | [N || N <- neighbours(Coord), not(lists:member(N, coordinates(Neighbours)))]. 226 | 227 | neighbours({X, Y}) -> 228 | [{X + DX, Y + DY} || DX <- [-1, 0, 1], DY <- [-1, 0, 1], {DX, DY} =/= {0, 0}]. 229 | 230 | %% ------------------------------------------------------------------ 231 | %% Data Abstractions 232 | %% 233 | %% Currently gb_trees are used. With Erlang 17 we can use frames. 234 | %% ------------------------------------------------------------------ 235 | 236 | new_world() -> gb_trees:empty(). 237 | 238 | population(World) -> gb_trees:size(World). 239 | 240 | add(Coord, Cell, World) -> gb_trees:insert(Coord, Cell, World). 241 | 242 | remove(Coord, World) -> gb_trees:delete(Coord, World). 243 | 244 | coordinates(World) -> gb_trees:keys(World). 245 | 246 | cells(World) -> gb_trees:values(World). 247 | 248 | -------------------------------------------------------------------------------- /src/life_async_grid.erl: -------------------------------------------------------------------------------- 1 | %%% 2 | %%% Asynchronous Game of Life 3 | %%% http://en.wikipedia.org/wiki/Conway's_Game_of_Life 4 | %%% http://en.wikipedia.org/wiki/Asynchronous_cellular_automaton 5 | %%% 6 | %%% This implementation can be run step by step, controlled from 7 | %%% the shell, in which case it preserves the classic game behaviour. 8 | %%% Or it can be run completely asynchronously until it's stopped by 9 | %%% 'stop' message. In this case the behaviour of the game is 10 | %%% eventually consistent, meaning that at any particular point 11 | %%% there might be cells on the grid from different generations, 12 | %%% which you wouldn't expect in the classic game. However, 13 | %%% since cells "synchronize" with each other, the results of the 14 | %%% async game "on average" will be the same as in the classic one. 15 | %%% 16 | %%% In either case, the default state of the grid can be examined 17 | %%% by running 'snapshot' command. 18 | %%% 19 | %%% To run the game forever, uncomment line 153. 20 | %%% 21 | %%% See also: 22 | %%% life.erl - standard game implementation, 23 | %%% life_async - async implementation with synchronization process. 24 | %%% 25 | -module(life_async_grid). 26 | -author("Andrey Paramonov "). 27 | 28 | %% Published API 29 | -export([new_game/1, make_alive/2, start/1, snapshot/1, stop/1]). 30 | 31 | %% Internal functions for process spawning 32 | -export([cell/1]). 33 | 34 | %% Cell state as a convenient data holder 35 | -record(cell, {coord, 36 | gen = 0, 37 | state = dead, 38 | prev_state = dead, 39 | response_count = 0, 40 | alive_count = 0, 41 | neighbours = []}). 42 | 43 | %% ------------------------------------------------------------------ 44 | %% Game Logic 45 | %% 46 | %% Grid = life_async_grid:new_game(10). 47 | %% life_async_grid:make_alive([{3,2},{3,3},{3,4}], Grid). 48 | %% life_async_grid:snapshot(Grid). 49 | %% life_async_grid:start(Grid). 50 | %% life_async_grid:snapshot(Grid). 51 | %% life_async_grid:stop(Grid). 52 | %% ------------------------------------------------------------------ 53 | 54 | %% @doc Creates a new grid of connected dead cells. 55 | %% Users should keep the reference to the grid to play the game. 56 | new_game(Size) -> 57 | Grid = new_grid(Size), 58 | connect_all(Grid), 59 | Grid. 60 | 61 | %% @doc Builds a new grid of disconnected dead cells. 62 | %% Grid is a hashmap where keys are coordinates and values are 63 | %% cell processes. 64 | new_grid(Size) -> 65 | lists:foldl( 66 | fun(Coord, Acc) -> 67 | Cell = spawn(?MODULE, cell, [#cell{coord = Coord}]), 68 | add(Coord, Cell, Acc) 69 | end, 70 | new_grid(), cartesian_plane(Size)). 71 | 72 | %% @doc Sends 'connect' message to all cells. 73 | %% It's done once during the initialization of the game. 74 | %% Upon receiving this message, cells will discover their 75 | %% neighbours and save reference to them. 76 | connect_all(Grid) -> 77 | ok = send(Grid, coordinates(Grid), {connect, Grid}). 78 | 79 | %% @doc Sends 'alive' message to specific cells. 80 | %% Upon receiving this message, cells will change their state to alive. 81 | make_alive(Coords, Grid) -> 82 | ok = send(Grid, Coords, alive). 83 | 84 | %% @doc Sends 'start' message to all cells. 85 | %% That will initiate the first step of the game. 86 | start(Grid) -> 87 | ok = send(Grid, coordinates(Grid), start). 88 | 89 | %% @doc Sends 'current_status' message to all cells, requesting 90 | %% their status. Blocks until receiving responses from all cells. 91 | %% Returns coordinates of all live cells together with their 92 | %% generation numbers. 93 | snapshot(Grid) -> 94 | All = coordinates(Grid), 95 | ok = send(Grid, All, {current_status, self()}), 96 | lists:foldl( 97 | fun(C, Acc) -> 98 | receive 99 | {current_status, C, alive, Gen} -> [{C,Gen}|Acc]; 100 | {current_status, C, dead, _} -> Acc 101 | end 102 | end, [], All). 103 | 104 | %% @doc Sends 'stop' message to all cells, forcing them to leave game. 105 | stop(Grid) -> 106 | ok = send(Grid, coordinates(Grid), stop). 107 | 108 | %% ------------------------------------------------------------------ 109 | %% Cell process 110 | %% ------------------------------------------------------------------ 111 | 112 | %% @doc Main loop of a cell is to respond to messages from shell 113 | %% and other cells. 114 | cell(State) -> 115 | CurrentGen = State#cell.gen, 116 | PrevGen = CurrentGen - 1, 117 | receive 118 | {connect, Grid} -> 119 | NCoords = neighbours(State#cell.coord, grid_size(Grid)), 120 | NewNeighbours = [cell(C, Grid) || C <- NCoords], 121 | cell(State#cell{neighbours = NewNeighbours}); 122 | alive -> 123 | cell(State#cell{state = alive}); 124 | {current_status, Sender} -> 125 | Sender ! {current_status, State#cell.coord, State#cell.state, State#cell.gen}, 126 | cell(State); 127 | {your_status, CurrentGen, Sender} -> 128 | Sender ! {my_status, State#cell.state}, 129 | cell(State); 130 | {your_status, PrevGen, Sender} -> 131 | Sender ! {my_status, State#cell.prev_state}, 132 | cell(State); 133 | start -> 134 | self() ! step, 135 | cell(State); 136 | step -> 137 | lists:foreach( 138 | fun(N) -> N ! {your_status, State#cell.gen, self()} end, 139 | State#cell.neighbours), 140 | cell(State); 141 | {my_status, NStatus} -> 142 | NewState = new_state(State, NStatus), 143 | self() ! {transition, NewState#cell.response_count, NewState#cell.alive_count}, 144 | cell(NewState); 145 | {transition, 8, NAlive} -> 146 | NewState = State#cell{state = new_status(State#cell.state, NAlive), 147 | prev_state = State#cell.state, 148 | gen = State#cell.gen + 1, 149 | response_count = 0, 150 | alive_count = 0}, 151 | ok = print_transition(NewState), 152 | %% uncomment to disable global clock 153 | %self() ! step, 154 | cell(NewState); 155 | {transition, _, _} -> 156 | cell(State); 157 | stop -> ok 158 | end. 159 | 160 | %% @doc Updates response count and live neighbours count. 161 | new_state(State, alive) -> 162 | State#cell{response_count = State#cell.response_count + 1, 163 | alive_count = State#cell.alive_count + 1}; 164 | new_state(State, dead) -> 165 | State#cell{response_count = State#cell.response_count + 1}. 166 | 167 | %% @doc Implementation of game rules. 168 | new_status(_, 3) -> alive; 169 | new_status(alive, 2) -> alive; 170 | new_status(_, _) -> dead. 171 | 172 | %% @doc Print state transition. 173 | %% If state hasn't changed, don't print it. 174 | print_transition(#cell{prev_state = alive, state = dead} = State) -> 175 | pretty_print(State); 176 | print_transition(#cell{prev_state = dead, state = alive} = State) -> 177 | pretty_print(State); 178 | print_transition(_) -> ok. 179 | 180 | pretty_print(#cell{state = State, coord = Coord, gen = Gen}) -> 181 | io:format("~p: ~p -> ~p~n", [Gen, Coord, State]). 182 | 183 | %% ------------------------------------------------------------------ 184 | %% Helper functions 185 | %% ------------------------------------------------------------------ 186 | 187 | %% @doc Returns cartesian square plane with side length of Size. 188 | cartesian_plane(Size) -> 189 | [{X,Y} || X <- seq(Size), Y <- seq(Size)]. 190 | 191 | seq(Size) -> lists:seq(0, Size - 1). 192 | 193 | grid_size(Grid) -> round(math:sqrt(population(Grid))). 194 | 195 | %% @doc The game grid is modelled as torus. 196 | neighbours({X, Y}, Size) -> 197 | [{mod(X + DX, Size), mod(Y + DY, Size)} || 198 | DX <- [-1, 0, 1], 199 | DY <- [-1, 0, 1], 200 | {DX, DY} =/= {0, 0}]. 201 | 202 | mod(X,Y) -> (X rem Y + Y) rem Y. 203 | 204 | %% @doc Sends Message to cells with specified Coords on the Grid. 205 | send(Grid, Coords, Message) -> 206 | lists:foreach( 207 | fun(Coord) -> 208 | cell(Coord, Grid) ! Message 209 | end, 210 | Coords), 211 | ok. 212 | 213 | %% ------------------------------------------------------------------ 214 | %% Data Abstractions 215 | %% 216 | %% Currently dict is used. With Erlang 17 we can use frames. 217 | %% ------------------------------------------------------------------ 218 | 219 | new_grid() -> dict:new(). 220 | 221 | population(Grid) -> dict:size(Grid). 222 | 223 | add(Coord, Cell, Grid) -> dict:store(Coord, Cell, Grid). 224 | 225 | coordinates(Grid) -> 226 | [Coord || {Coord, _} <- dict:to_list(Grid)]. 227 | 228 | cell(Coord, Grid) -> dict:fetch(Coord, Grid). 229 | 230 | -------------------------------------------------------------------------------- /src/list_server.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% In the shell type: 3 | %% 4 | %% 1> register(list_server, spawn(list_server, start, [])). 5 | %% 2> list_server ! {self(), sort, [9,8,7,6,5,4,3,2,1]}. 6 | %% 3> flush(). 7 | %% 8 | -module(list_server). 9 | -export([start/0]). 10 | 11 | start() -> 12 | receive 13 | {From, sort, List} -> 14 | From ! lists:sort(List), 15 | start(); 16 | stop -> 17 | true 18 | % 19 | % Uncomment this code to fix memory leakage 20 | % 21 | % _Msg -> 22 | % io:format("Received message: ~w~n", [_Msg]), 23 | % start() 24 | end. -------------------------------------------------------------------------------- /src/log_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.136. 3 | %% Event Manager Pattern. 4 | %% 5 | %% See also event_manager.erl 6 | %% 7 | -module(log_handler). 8 | -export([init/1, terminate/1, handle_event/2]). 9 | 10 | init(File) -> 11 | {ok, Fd} = file:open(File, write), 12 | Fd. 13 | 14 | terminate(Fd) -> file:close(Fd). 15 | 16 | handle_event({Action, Id, Event}, Fd) -> 17 | {MegaSec, Sec, MicroSec} = erlang:timestamp(), 18 | io:format(Fd, "~w,~w,~w,~w,~w,~p~n", [MegaSec, Sec, MicroSec, Action, Id, Event]), 19 | Fd; 20 | 21 | handle_event(_, Fd) -> Fd. 22 | -------------------------------------------------------------------------------- /src/mutex.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.129. 3 | %% FSM Pattern 4 | %% 5 | %% 1> mutex:start(). 6 | %% 2> mutex:wait(). 7 | %% 3> mutex:signal(). 8 | %% 4> mutex:stop(). 9 | %% 10 | -module(mutex). 11 | -export([start/0, stop/0]). 12 | -export([wait/0, signal/0]). 13 | -export([init/0]). 14 | 15 | start() -> register(mutex, spawn(?MODULE, init, [])). 16 | 17 | % Initial state 18 | 19 | init() -> free(). 20 | 21 | % Events 22 | 23 | wait() -> 24 | mutex ! {wait, self()}, 25 | receive ok -> ok end. 26 | 27 | signal() -> mutex ! {signal, self()}, ok. 28 | 29 | stop() -> mutex ! stop. 30 | 31 | % States and transitions 32 | 33 | free() -> 34 | receive 35 | {wait, Pid} -> 36 | Pid ! ok, 37 | busy(Pid); 38 | stop -> 39 | terminate() 40 | end. 41 | 42 | busy(Pid) -> 43 | receive 44 | {signal, Pid} -> free() 45 | end. 46 | 47 | terminate() -> 48 | receive 49 | {wait, Pid} -> 50 | exit(Pid, kill), 51 | terminate() 52 | after 53 | 0 -> ok 54 | end. -------------------------------------------------------------------------------- /src/mutex_monitor.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.154. 3 | %% Exercise 6-2: Reliable Mutex Semaphore 4 | %% 5 | %% See also: mutex_reliable.erl 6 | %% 7 | -module(mutex_monitor). 8 | -export([start/0, stop/0]). 9 | -export([wait/0, signal/0]). 10 | -export([init/0]). 11 | 12 | start() -> 13 | register(mutex_monitor, spawn(?MODULE, init, [])). 14 | 15 | stop() -> 16 | mutex_monitor ! stop. 17 | 18 | init() -> 19 | % process_flag(trap_exit, true), 20 | free(). 21 | 22 | % Events 23 | 24 | wait() -> 25 | mutex_monitor ! {wait, self()}, 26 | receive ok -> ok end. 27 | 28 | signal() -> 29 | mutex_monitor ! {signal, self()}, ok. 30 | 31 | % States and transitions 32 | 33 | free() -> 34 | receive 35 | {wait, Pid} -> 36 | io:format("Monitoring and locking for ~p~n", [Pid]), 37 | Reference = erlang:monitor(process, Pid), 38 | Pid ! ok, 39 | busy(Pid, Reference); 40 | % {'EXIT', _Pid, normal} -> 41 | % free(); 42 | stop -> 43 | terminate() 44 | end. 45 | 46 | busy(Pid, Reference) -> 47 | receive 48 | {signal, Pid} -> 49 | io:format("Signal from and demonitor ~p~n", [Pid]), 50 | erlang:demonitor(Reference, [flush]), 51 | free(); 52 | {'DOWN', Reference, process, Pid, Reason} -> 53 | io:format("Waiting process died ~p: ~s~n", [Pid, Reason]), 54 | free() 55 | end. 56 | 57 | terminate() -> 58 | receive 59 | {wait, Pid} -> 60 | exit(Pid, kill), 61 | terminate() 62 | after 63 | 0 -> ok 64 | end. 65 | -------------------------------------------------------------------------------- /src/mutex_monitor_test.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.154. 3 | %% Exercise 6-2: Reliable Mutex Semaphore 4 | %% 5 | %% See also: mutex_reliable_test.erl 6 | %% 7 | %% 1> mutex_monitor:start(). 8 | %% true 9 | %% 10 | -module(mutex_monitor_test). 11 | -export([test1/0, test2/0]). 12 | -export([first/0, second/0]). 13 | 14 | first() -> 15 | io:format("~p requesting mutex~n", [self()]), 16 | mutex_monitor:wait(), 17 | receive cont -> ok end, 18 | io:format("~p releasing mutex~n", [self()]), 19 | mutex_monitor:signal(). 20 | 21 | second() -> 22 | io:format("~p requesting mutex~n", [self()]), 23 | mutex_monitor:wait(), 24 | mutex_monitor:signal(). 25 | 26 | %% 2> mutex_monitor_test:test1(). 27 | %% <0.35.0> requesting mutex 28 | %% <0.36.0> requesting mutex 29 | %% Monitoring and locking for <0.35.0> 30 | %% <0.35.0> releasing mutex 31 | %% ok 32 | %% Signal from and demonitor <0.35.0> 33 | %% Monitoring and locking for <0.36.0> 34 | %% Waiting process died <0.36.0>: noproc 35 | %% 36 | test1() -> 37 | F = spawn(mutex_monitor_test, first, []), 38 | S = spawn(mutex_monitor_test, second, []), 39 | receive after 1000 -> ok end, 40 | exit(S, kill), 41 | F ! cont, 42 | ok. 43 | 44 | %% 3> mutex_monitor_test:test2(). 45 | %% <0.38.0> requesting mutex 46 | %% <0.39.0> requesting mutex 47 | %% Monitoring and locking for <0.38.0> 48 | %% Waiting process died <0.38.0>: killed 49 | %% ok 50 | %% Monitoring and locking for <0.39.0> 51 | %% Signal from and demonitor <0.39.0> 52 | %% 53 | test2() -> 54 | F = spawn(mutex_monitor_test, first, []), 55 | spawn(mutex_monitor_test, second, []), 56 | receive after 1000 -> ok end, 57 | exit(F, kill), 58 | ok. 59 | -------------------------------------------------------------------------------- /src/mutex_reliable.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.154. 3 | %% Exercise 6-2: Reliable Mutex Semaphore 4 | %% 5 | %% See: http://groups.google.com/group/erlang-programming-book/browse_thread/thread/1d26b447f9db8f54 6 | %% 7 | -module(mutex_reliable). 8 | -export([start/0, stop/0]). 9 | -export([wait/0, signal/0]). 10 | -export([init/0]). 11 | 12 | start() -> 13 | register(mutex_reliable, spawn(?MODULE, init, [])). 14 | 15 | stop() -> 16 | mutex_reliable ! stop. 17 | 18 | init() -> 19 | process_flag(trap_exit, true), 20 | free(). 21 | 22 | % Events 23 | 24 | wait() -> 25 | mutex_reliable ! {wait, self()}, 26 | receive ok -> ok end. 27 | 28 | signal() -> 29 | mutex_reliable ! {signal, self()}, ok. 30 | 31 | % States and transitions 32 | 33 | free() -> 34 | receive 35 | {wait, Pid} -> 36 | io:format("linking to and locking for ~p~n", [Pid]), 37 | link(Pid), 38 | Pid ! ok, 39 | busy(Pid); 40 | {'EXIT', _Pid, normal} -> 41 | free(); 42 | stop -> 43 | terminate() 44 | end. 45 | 46 | busy(Pid) -> 47 | receive 48 | {signal, Pid} -> 49 | io:format("signal from/unlinking ~p~n", [Pid]), 50 | unlink(Pid), 51 | free(); 52 | {'EXIT', Pid, _Reason} -> 53 | io:format("waiting process died ~p~n", [Pid]), 54 | free() 55 | end. 56 | 57 | terminate() -> 58 | receive 59 | {wait, Pid} -> 60 | exit(Pid, kill), 61 | terminate() 62 | after 63 | 0 -> ok 64 | end. 65 | -------------------------------------------------------------------------------- /src/mutex_reliable_test.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.154. 3 | %% Exercise 6-2: Reliable Mutex Semaphore 4 | %% 5 | %% See: http://groups.google.com/group/erlang-programming-book/browse_thread/thread/1d26b447f9db8f54 6 | %% 7 | %% 1> mutex_reliable:start(). 8 | %% true 9 | %% 10 | -module(mutex_reliable_test). 11 | -export([test1/0, test2/0]). 12 | -export([first/0, second/0]). 13 | 14 | first() -> 15 | io:format("~p requesting mutex~n", [self()]), 16 | mutex_reliable:wait(), 17 | receive cont -> ok end, 18 | io:format("~p releasing mutex~n", [self()]), 19 | mutex_reliable:signal(). 20 | 21 | second() -> 22 | io:format("~p requesting mutex~n", [self()]), 23 | mutex_reliable:wait(), 24 | mutex_reliable:signal(). 25 | 26 | %% 2> mutex_reliable_test:test1(). 27 | %% <0.35.0> requesting mutex 28 | %% <0.36.0> requesting mutex 29 | %% linking to and locking for <0.35.0> 30 | %% <0.35.0> releasing mutex 31 | %% ok 32 | %% signal from/unlinking <0.35.0> 33 | %% linking to and locking for <0.36.0> 34 | %% waiting process died <0.36.0> 35 | %% 36 | test1() -> 37 | F = spawn(mutex_reliable_test, first, []), 38 | S = spawn(mutex_reliable_test, second, []), 39 | receive after 1000 -> ok end, 40 | exit(S, kill), 41 | F ! cont, 42 | ok. 43 | 44 | %% 3> mutex_reliable_test:test2(). 45 | %% <0.38.0> requesting mutex 46 | %% <0.39.0> requesting mutex 47 | %% linking to and locking for <0.38.0> 48 | %% waiting process died <0.38.0> 49 | %% ok 50 | %% linking to and locking for <0.39.0> 51 | %% signal from/unlinking <0.39.0> 52 | %% 53 | test2() -> 54 | F = spawn(mutex_reliable_test, first, []), 55 | spawn(mutex_reliable_test, second, []), 56 | receive after 1000 -> ok end, 57 | exit(F, kill), 58 | ok. 59 | -------------------------------------------------------------------------------- /src/my_db.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.137. 3 | %% Exercise 5-1: Database Server 4 | %% 5 | %% 1> my_db:start(). 6 | %% ok 7 | %% 2> my_db:write(foo,bar). 8 | %% ok 9 | %% 3> my_db:read(baz). 10 | %% {error, instance} 11 | %% 4> my_db:read(foo). 12 | %% {ok, bar} 13 | %% 5> my_db:match(bar). 14 | %% [foo] 15 | %% 16 | -module(my_db). 17 | -export([start/0, stop/0]). 18 | -export([write/2, delete/1, read/1, match/1]). 19 | -export([init/0]). 20 | 21 | % Start/stop functions 22 | 23 | start() -> 24 | register(my_db, spawn(?MODULE, init, [])), ok. 25 | 26 | stop() -> 27 | my_db ! stop, ok. 28 | 29 | init() -> 30 | loop(db:new()). 31 | 32 | % Functional interface 33 | 34 | write(Key, Element) -> 35 | call(create, {Key, Element}). 36 | 37 | delete(Key) -> 38 | call(delete, Key). 39 | 40 | read(Key) -> 41 | call(read, Key). 42 | 43 | match(Element) -> 44 | call(find_by_element, Element). 45 | 46 | call(Command, Parameter) -> 47 | my_db ! {request, self(), Command, Parameter}, 48 | receive {reply, Reply} -> Reply end. 49 | 50 | % Main loop 51 | 52 | loop(Db) -> 53 | receive 54 | stop -> 55 | db:destroy(Db); 56 | {request, Pid, create, {Key, Element}} -> 57 | NewDb = db:write(Key, Element, Db), 58 | reply(Pid, ok), 59 | loop(NewDb); 60 | {request, Pid, delete, Key} -> 61 | NewDb = db:delete(Key, Db), 62 | reply(Pid, ok), 63 | loop(NewDb); 64 | {request, Pid, read, Key} -> 65 | Entry = db:read(Key, Db), 66 | reply(Pid, Entry), 67 | loop(Db); 68 | {request, Pid, find_by_element, Element} -> 69 | Entries = db:match(Element, Db), 70 | reply(Pid, Entries), 71 | loop(Db) 72 | end. 73 | 74 | reply(Pid, Msg) -> Pid ! {reply, Msg}. 75 | -------------------------------------------------------------------------------- /src/my_lib.erl: -------------------------------------------------------------------------------- 1 | -module(my_lib). 2 | -export([even/1]). 3 | 4 | even(Int) when Int rem 2 == 0 -> true; 5 | even(Int) when Int rem 2 == 1 -> false. 6 | 7 | %END:area 8 | -------------------------------------------------------------------------------- /src/my_lists.erl: -------------------------------------------------------------------------------- 1 | -module(my_lists). 2 | -export([qsort/1, keyfilter/3]). 3 | 4 | qsort([]) -> []; 5 | qsort([X|Xs]) -> qsort([Y || Y<-Xs, Y=X]). 6 | 7 | keyfilter(Key, N, TupleList) -> 8 | lists:filter(fun(Elem) -> tuple_match(Key, N, Elem) end, TupleList). 9 | 10 | tuple_match(Key, N, Tuple) -> 11 | case lists:keyfind(Key, N, [Tuple]) of 12 | Tuple -> true; 13 | false -> false 14 | end. -------------------------------------------------------------------------------- /src/my_supervisor.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.153. 3 | %% Supervisor Pattern. 4 | %% 5 | %% 1> my_supervisor:start_link(my_supervisor, [{add_two, start, []}]). 6 | %% ok 7 | %% 2> whereis(add_two). 8 | %% <0.34.0> 9 | %% 3> exit(whereis(add_two), kill). 10 | %% true 11 | %% 4> add_two:request(100). 12 | %% 102 13 | %% 5> whereis(add_two). 14 | %% <0.37.0> 15 | %% 6> my_supervisor:stop(my_supervisor). 16 | %% ok 17 | %% 7> whereis(add_two). 18 | %% undefined 19 | %% 20 | -module(my_supervisor). 21 | -export([start_link/2, stop/1]). 22 | -export([init/1]). 23 | 24 | start_link(Name, ChildSpecList) -> 25 | register(Name, spawn_link(my_supervisor, init, [ChildSpecList])), ok. 26 | 27 | init(ChildSpecList) -> 28 | process_flag(trap_exit, true), 29 | loop(start_children(ChildSpecList)). 30 | 31 | start_children([]) -> []; 32 | start_children([{M,F,A} | ChildSpecList]) -> 33 | case (catch apply(M, F, A)) of 34 | {ok, Pid} -> 35 | [{Pid, {M,F,A}} | start_children(ChildSpecList)]; 36 | _ -> 37 | start_children(ChildSpecList) 38 | end. 39 | 40 | loop(ChildList) -> 41 | receive 42 | {'EXIT', Pid, _Reason} -> 43 | NewChildList = restart_child(Pid, ChildList), 44 | loop(NewChildList); 45 | {stop, From} -> 46 | From ! {reply, terminate(ChildList)} 47 | end. 48 | 49 | restart_child(Pid, ChildList) -> 50 | {value, {Pid, {M,F,A}}} = lists:keysearch(Pid, 1, ChildList), 51 | {ok, NewPid} = apply(M,F,A), 52 | [{NewPid, {M,F,A}} | lists:keydelete(Pid, 1, ChildList)]. 53 | 54 | terminate([{Pid, _} | ChildList]) -> 55 | exit(Pid, kill), 56 | terminate(ChildList); 57 | terminate(_ChildList) -> ok. 58 | 59 | stop(Name) -> 60 | Name ! {stop, self()}, 61 | receive {reply, Reply} -> Reply end. 62 | -------------------------------------------------------------------------------- /src/my_supervisor2.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.155. 3 | %% Exercise 6-3: Supervisor Process 4 | %% 5 | -module(my_supervisor2). 6 | -export([start_link/2, stop/1]). 7 | -export([start_child/4, stop_child/2]). 8 | -export([init/1]). 9 | 10 | start_link(Name, ChildSpecList) -> 11 | start_id_generator(), 12 | register(Name, spawn_link(my_supervisor2, init, [ChildSpecList])), ok. 13 | 14 | start_id_generator() -> 15 | case whereis(id_generator) of 16 | undefined -> id_generator:start(); 17 | _ -> ok 18 | end. 19 | 20 | init(ChildSpecList) -> 21 | process_flag(trap_exit, true), 22 | loop(start_children(ChildSpecList)). 23 | 24 | start_children([]) -> []; 25 | start_children([{M,F,A,T,C} | ChildSpecList]) -> 26 | Id = id_generator:next(), 27 | case (catch apply(M, F, A)) of 28 | {ok, Pid} -> 29 | io:format("~p started; id=~p; spec=~p~n", [Pid, Id, {M,F,A,T,C}]), 30 | [{Pid, Id, {M,F,A,T,C}} | start_children(ChildSpecList)]; 31 | _ -> 32 | io:format("Skipped spec=~p~n", [{M,F,A,T,C}]), 33 | start_children(ChildSpecList) 34 | end. 35 | 36 | loop(ChildList) -> 37 | receive 38 | {'EXIT', Pid, normal} -> 39 | NewChildList = restart_permanent_child(Pid, ChildList), 40 | loop(NewChildList); 41 | {'EXIT', Pid, _Reason} -> 42 | NewChildList = restart_child(Pid, ChildList), 43 | loop(NewChildList); 44 | {start_child, From, {Module, Function, Arguments}} -> 45 | NewChildList = start_transient_child({Module, Function, Arguments}, ChildList), 46 | [NewChild | _Rest] = NewChildList, 47 | From ! {reply, NewChild}, 48 | loop(NewChildList); 49 | {stop_child, From, Id} -> 50 | NewChildList = stop_transient_child(Id, ChildList), 51 | From ! {reply, ok}, 52 | loop(NewChildList); 53 | {stop, From} -> 54 | From ! {reply, terminate(ChildList)} 55 | end. 56 | 57 | start_transient_child({M, F, A}, ChildList) -> 58 | Id = id_generator:next(), 59 | case (catch apply(M, F, A)) of 60 | {ok, Pid} -> 61 | [{Pid, Id, {M, F, A, transient, 5}} | ChildList]; 62 | _ -> 63 | ChildList 64 | end. 65 | 66 | stop_transient_child(Id, ChildList) -> 67 | case lists:keysearch(Id, 2, ChildList) of 68 | {value, {Pid, Id, {_M, _F, _A, _T, _C}}} -> 69 | unlink(Pid), 70 | exit(Pid, kill), 71 | lists:keydelete(Id, 2, ChildList); 72 | _ -> 73 | ChildList 74 | end. 75 | 76 | restart_permanent_child(Pid, ChildList) -> 77 | case lists:keysearch(Pid, 1, ChildList) of 78 | {value, {Pid, _Id, {_M,_F,_A,_T,0}}} -> 79 | io:format("Process ~p reached restart threshold~n", [Pid]), 80 | lists:keydelete(Pid, 1, ChildList); 81 | {value, {Pid, Id, {M,F,A,permanent,C}}} -> 82 | {ok, NewPid} = apply(M,F,A), 83 | [{NewPid, Id, {M,F,A,permanent,C-1}} | lists:keydelete(Pid, 1, ChildList)]; 84 | _ -> 85 | lists:keydelete(Pid, 1, ChildList) 86 | end. 87 | 88 | restart_child(Pid, ChildList) -> 89 | case lists:keysearch(Pid, 1, ChildList) of 90 | {value, {Pid, _Id, {_M,_F,_A,_T,0}}} -> 91 | io:format("Process ~p reached restart threshold~n", [Pid]), 92 | lists:keydelete(Pid, 1, ChildList); 93 | {value, {Pid, Id, {M,F,A,T,C}}} -> 94 | {ok, NewPid} = apply(M,F,A), 95 | [{NewPid, Id, {M,F,A,T,C-1}} | lists:keydelete(Pid, 1, ChildList)] 96 | end. 97 | 98 | terminate([{Pid, _, _} | ChildList]) -> 99 | exit(Pid, kill), 100 | terminate(ChildList); 101 | terminate(_ChildList) -> ok. 102 | 103 | stop(Name) -> 104 | Name ! {stop, self()}, 105 | receive {reply, Reply} -> Reply end. 106 | 107 | start_child(Name, Module, Function, Arguments) -> 108 | Name ! {start_child, self(), {Module, Function, Arguments}}, 109 | receive {reply, Reply} -> Reply end. 110 | 111 | stop_child(Name, Id) -> 112 | Name ! {stop_child, self(), Id}, 113 | receive {reply, Reply} -> Reply end. 114 | -------------------------------------------------------------------------------- /src/my_supervisor2_test.erl: -------------------------------------------------------------------------------- 1 | -module(my_supervisor2_test). 2 | -export([test1/0, test2/0, test3/0, test4/0]). 3 | -export([test5/0, test6/0]). 4 | 5 | % 1> my_supervisor2_test:test1(). 6 | % Spawned permanent process <0.34.0> 7 | % Killing process <0.34.0> 8 | % Where is our process? <0.35.0> 9 | % ok 10 | test1() -> 11 | my_supervisor2:start_link(my_supervisor2, [{echo3, start, [], permanent, 5}]), 12 | receive after 1000 -> ok end, 13 | io:format("Spawned permanent process ~p~n", [whereis(echo3)]), 14 | io:format("Killing process ~p~n", [whereis(echo3)]), 15 | exit(whereis(echo3), kill), 16 | receive after 1000 -> ok end, 17 | io:format("Where is our process? ~p~n", [whereis(echo3)]), 18 | my_supervisor2:stop(my_supervisor2). 19 | 20 | % 1> my_supervisor2_test:test2(). 21 | % Spawned permanent process <0.40.0> 22 | % Exiting process <0.40.0> 23 | % Where is our process? <0.41.0> 24 | % ok 25 | test2() -> 26 | my_supervisor2:start_link(my_supervisor2, [{echo3, start, [], permanent, 5}]), 27 | receive after 1000 -> ok end, 28 | io:format("Spawned permanent process ~p~n", [whereis(echo3)]), 29 | io:format("Exiting process ~p~n", [whereis(echo3)]), 30 | echo3 ! stop, 31 | receive after 1000 -> ok end, 32 | io:format("Where is our process? ~p~n", [whereis(echo3)]), 33 | my_supervisor2:stop(my_supervisor2). 34 | 35 | % 1> my_supervisor2_test:test3(). 36 | % Spawned transient process <0.34.0> 37 | % Killing process <0.34.0> 38 | % Where is our process? <0.35.0> 39 | % ok 40 | test3() -> 41 | my_supervisor2:start_link(my_supervisor2, [{echo3, start, [], transient, 5}]), 42 | receive after 1000 -> ok end, 43 | io:format("Spawned transient process ~p~n", [whereis(echo3)]), 44 | io:format("Killing process ~p~n", [whereis(echo3)]), 45 | exit(whereis(echo3), kill), 46 | receive after 1000 -> ok end, 47 | io:format("Where is our process? ~p~n", [whereis(echo3)]), 48 | my_supervisor2:stop(my_supervisor2). 49 | 50 | % 1> my_supervisor2_test:test4(). 51 | % Spawned transient process <0.40.0> 52 | % Exiting process <0.40.0> 53 | % Where is our process? undefined 54 | % ok 55 | test4() -> 56 | my_supervisor2:start_link(my_supervisor2, [{echo3, start, [], transient, 5}]), 57 | receive after 1000 -> ok end, 58 | io:format("Spawned transient process ~p~n", [whereis(echo3)]), 59 | io:format("Exiting process ~p~n", [whereis(echo3)]), 60 | echo3 ! stop, 61 | receive after 1000 -> ok end, 62 | io:format("Where is our process? ~p~n", [whereis(echo3)]), 63 | my_supervisor2:stop(my_supervisor2). 64 | 65 | % 1> my_supervisor2_test:test5(). 66 | % Spawned permanent process <0.49.0> 67 | % Exiting process <0.49.0> 68 | % Where is our process? <0.50.0> 69 | % Spawned permanent process <0.50.0> 70 | % Exiting process <0.50.0> 71 | % Where is our process? <0.51.0> 72 | % Spawned permanent process <0.51.0> 73 | % Exiting process <0.51.0> 74 | % Where is our process? <0.52.0> 75 | % Spawned permanent process <0.52.0> 76 | % Exiting process <0.52.0> 77 | % Where is our process? <0.53.0> 78 | % Spawned permanent process <0.53.0> 79 | % Exiting process <0.53.0> 80 | % Where is our process? <0.54.0> 81 | % Spawned permanent process <0.54.0> 82 | % Exiting process <0.54.0> 83 | % Process <0.54.0> reached restart threshold 84 | % ok 85 | test5() -> 86 | my_supervisor2:start_link(my_supervisor2, [{echo3, start, [], permanent, 5}]), 87 | receive after 1000 -> ok end, 88 | test5_loop(), 89 | my_supervisor2:stop(my_supervisor2). 90 | 91 | test5_loop() -> 92 | io:format("Spawned permanent process ~p~n", [whereis(echo3)]), 93 | io:format("Exiting process ~p~n", [whereis(echo3)]), 94 | echo3 ! stop, 95 | receive after 1000 -> ok end, 96 | case whereis(echo3) of 97 | undefined -> ok; 98 | Pid -> 99 | io:format("Where is our process? ~p~n", [Pid]), 100 | test5_loop() 101 | end. 102 | 103 | % 1> my_supervisor2_test:test6(). 104 | % Starting {echo3,start,[]} 105 | % Spawned process {<0.35.0>,0,{echo3,start,[],transient,5}} 106 | % Stopping process <0.35.0> 107 | % Where is our process? undefined 108 | % ok 109 | test6() -> 110 | my_supervisor2:start_link(my_supervisor2, []), 111 | io:format("Starting ~p~n", [{echo3, start, []}]), 112 | {Pid, Id, Spec} = my_supervisor2:start_child(my_supervisor2, echo3, start, []), 113 | io:format("Spawned process ~p~n", [{Pid, Id, Spec}]), 114 | io:format("Stopping process ~p~n", [whereis(echo3)]), 115 | my_supervisor2:stop_child(my_supervisor2, Id), 116 | receive after 1000 -> ok end, 117 | io:format("Where is our process? ~p~n", [whereis(echo3)]), 118 | my_supervisor2:stop(my_supervisor2). 119 | -------------------------------------------------------------------------------- /src/myring.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% In the shell type: 3 | %% 4 | %% 1> timer:tc(myring, start, [100000]). 5 | %% 2> timer:tc(myring, start, [1000000]). 6 | %% 3> timer:tc(myring, start, [10000000]). 7 | %% 8 | -module(myring). 9 | -export([start/1, start_proc/2]). 10 | 11 | start(Num) -> 12 | start_proc(Num, self()). 13 | 14 | start_proc(0, Pid) -> 15 | Pid ! ok; 16 | 17 | start_proc(Num, Pid) -> 18 | NPid = spawn(?MODULE, start_proc, [Num-1, Pid]), 19 | NPid ! ok, 20 | receive ok -> ok end. -------------------------------------------------------------------------------- /src/phone.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.138. 3 | %% Exercise 5-5: Phone FSM 4 | %% 5 | %% 1> phone:start(). 6 | %% 7 | %% 2> phone:incoming("555-111-2222"). 8 | %% 3> phone:other_on_hook("555-111-2222"). 9 | %% log incoming call 10 | %% 11 | %% 4> phone:incoming("555-333-4444"). 12 | %% 5> phone:off_hook("555-333-4444"). 13 | %% 6> phone:on_hook("555-333-4444"). 14 | %% log incoming call and its duration 15 | %% 16 | %% 7> phone:off_hook(). 17 | %% 8> phone:on_hook(). 18 | %% no log entry 19 | %% 20 | %% 9> phone:off_hook(). 21 | %% 10> phone:connect("555-555-6666"). 22 | %% 11> phone:on_hook("555-555-6666"). 23 | %% log outcoming call and its duration 24 | %% 25 | %% 12> phone:stop(). 26 | %% 27 | -module(phone). 28 | -export([start/0, stop/0]). 29 | -export([incoming/1, off_hook/0, off_hook/1, other_on_hook/1, on_hook/0, on_hook/1, connect/1]). 30 | -export([init/0]). 31 | 32 | start() -> 33 | event_manager:start(billing, [{log_handler, "phone.log"}]), 34 | register(phone, spawn(?MODULE, init, [])), 35 | ok. 36 | 37 | stop() -> 38 | event_manager:stop(billing), 39 | ok. 40 | 41 | % Initial state 42 | 43 | init() -> idle(). 44 | 45 | % Events 46 | 47 | incoming(Number) -> phone ! {incoming, Number}. 48 | off_hook() -> phone ! off_hook. 49 | off_hook(Number) -> phone ! {off_hook, Number}. 50 | other_on_hook(Number) -> phone ! {other_on_hook, Number}. 51 | on_hook() -> phone ! on_hook. 52 | on_hook(Number) -> phone ! {on_hook, Number}. 53 | connect(Number) -> phone ! {other_off_hook, Number}. 54 | 55 | % States and transitions 56 | 57 | idle() -> 58 | receive 59 | {incoming, Number} -> 60 | start_ringing(Number), 61 | ringing(Number); 62 | off_hook -> 63 | start_tone(), 64 | dial() 65 | end. 66 | 67 | ringing(Number) -> 68 | receive 69 | {other_on_hook, Number} -> 70 | stop_ringing(), 71 | idle(); 72 | {off_hook, Number} -> 73 | stop_ringing(Number), 74 | connected(Number) 75 | end. 76 | 77 | connected(Number) -> 78 | receive 79 | {on_hook, Number} -> 80 | stop_conversation(Number), 81 | idle() 82 | end. 83 | 84 | dial() -> 85 | receive 86 | on_hook -> 87 | stop_tone(), 88 | idle(); 89 | {other_off_hook, Number} -> 90 | start_conversation(Number), 91 | connected(Number) 92 | end. 93 | 94 | % Actions 95 | 96 | start_ringing(Number) -> event_manager:send_event(billing, {incoming, 10, Number}). 97 | stop_ringing() -> ok. 98 | stop_ringing(Number) -> event_manager:send_event(billing, {accept_incoming, 20, Number}). 99 | start_tone() -> ok. 100 | stop_tone() -> ok. 101 | start_conversation(Number) -> event_manager:send_event(billing, {start_outgoing, 20, Number}). 102 | stop_conversation(Number) -> event_manager:send_event(billing, {stop_conversation, 30, Number}). 103 | -------------------------------------------------------------------------------- /src/poisson.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% http://www.computer.org/csdl/mags/cs/2012/06/mcs2012060008.html 3 | %% http://steve.vinoski.net/pdf/CISE-Vinoski-Concurrency-and-Message-Passing-in-Erlang.pdf 4 | %% 5 | 6 | -module(poisson). 7 | 8 | %% API 9 | -export([poisson1d/0]). 10 | 11 | %% Internal exports 12 | -export([phi/4]). 13 | 14 | 15 | poisson1d() -> 16 | NSites = 16, 17 | H = 0.1, 18 | NIters = 10, 19 | Collector = self(), 20 | Sentinel = spawn(fun sentinel/0), 21 | Pids = lists:foldl( 22 | fun(I, [Prev|_]=Acc) -> 23 | Rho = case NSites div 2 of 24 | I -> 1; 25 | _ -> 0 26 | end, 27 | Args = [Collector, NIters, Prev, {I,H,Rho}], 28 | Pid = spawn(?MODULE, phi, Args), 29 | Prev ! {set_next, Pid}, 30 | [Pid | Acc] 31 | end, 32 | [Sentinel], 33 | lists:seq(0, NSites-1)), 34 | hd(Pids) ! {set_next, Sentinel}, 35 | lists:foreach( 36 | fun(Pid) -> 37 | receive 38 | {Pid, I, Phi} -> 39 | io:format("~2w ~.10f~n", [I, Phi]) 40 | end 41 | end, 42 | tl(lists:reverse(Pids))), 43 | Sentinel ! stop, 44 | ok. 45 | 46 | phi(Collector, NIters, Prev, Consts) -> 47 | receive 48 | {set_next, Next} -> 49 | phi(NIters, Collector, Prev, Next, Consts, 0.0) 50 | end. 51 | 52 | phi(0, Collector, _, _, {I, _, _}, Phi) -> 53 | Collector ! {self(), I, Phi}; 54 | phi(NIters, Collector, Prev, Next, {_, H, Rho}=Consts, Phi) -> 55 | PhiPrev = adjacent_phi(Prev, Phi), 56 | PhiNext = adjacent_phi(Next, Phi), 57 | NewPhi = (PhiPrev + PhiNext)/2 + H/2 * Rho, 58 | phi(NIters-1, Collector, Prev, Next, Consts, NewPhi). 59 | 60 | adjacent_phi(Adjacent, Phi) -> 61 | Adjacent ! {put, Phi, self()}, 62 | receive 63 | {put, AdjacentPhi, Adjacent} -> 64 | AdjacentPhi 65 | end. 66 | 67 | sentinel() -> 68 | receive 69 | {set_next, _} -> 70 | ok; 71 | {put, _, Pid} -> 72 | Pid ! {put, 0.0, self()}; 73 | stop -> 74 | exit(normal) 75 | end, 76 | sentinel(). 77 | -------------------------------------------------------------------------------- /src/records1.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.168. 3 | % Exercise 7-1: Extending Records 4 | % 5 | -module(records1). 6 | -export([birthday/1, joe/0, showPerson/1]). 7 | 8 | -record(person, {name, age = 0, phone, address}). 9 | 10 | birthday(#person{age = Age} = P) -> 11 | P#person{age = Age + 1}. 12 | 13 | joe() -> 14 | #person{name = "Joe", age = 21, phone = "999-9999"}. 15 | 16 | showPerson(#person{age = Age, phone = Phone, name = Name, address = Address}) -> 17 | io:format("name: ~p, age: ~p, phone: ~p, address: ~p~n", [Name, Age, Phone, Address]). 18 | -------------------------------------------------------------------------------- /src/records1_test.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.168. 3 | % Exercise 7-1: Extending Records 4 | % 5 | -module(records1_test). 6 | -export([test1/0]). 7 | 8 | % 1> records1_test:test1(). 9 | % 10 | test1() -> 11 | Joe = records1:joe(), 12 | records1:showPerson(Joe). 13 | -------------------------------------------------------------------------------- /src/shapes.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.169. 3 | % Exercise 7-4: Records and Shapes 4 | % 5 | -module(shapes). 6 | -export([area/1, perimeter/1]). 7 | -include("Shapes.hrl"). 8 | 9 | area(Shape) when is_record(Shape, circle) -> 10 | #circle{radius = Radius} = Shape, 11 | math:pi() * Radius * Radius; 12 | 13 | area(Shape) when is_record(Shape, rectangle) -> 14 | #rectangle{length = Length, width = Width} = Shape, 15 | Length * Width; 16 | 17 | area(Shape) when is_record(Shape, triangle) -> 18 | #triangle{a = A, b = B, c = C} = Shape, 19 | S = perimeter(Shape) / 2, 20 | math:sqrt(S * (S - A) * (S - B) * (S - C)). 21 | 22 | perimeter(Shape) when is_record(Shape, circle) -> 23 | #circle{radius = Radius} = Shape, 24 | 2 * math:pi() * Radius; 25 | 26 | perimeter(Shape) when is_record(Shape, rectangle) -> 27 | #rectangle{length = Length, width = Width} = Shape, 28 | 2 * (Length + Width); 29 | 30 | perimeter(Shape) when is_record(Shape, triangle) -> 31 | #triangle{a = A, b = B, c = C} = Shape, 32 | A + B + C. 33 | -------------------------------------------------------------------------------- /src/shapes_test.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.169. 3 | % Exercise 7-4: Records and Shapes 4 | % 5 | -module(shapes_test). 6 | -export([test/0]). 7 | -include("Shapes.hrl"). 8 | 9 | test() -> 10 | area_circle(), 11 | area_rectangle(), 12 | area_triangle(), 13 | perimeter_circle(), 14 | perimeter_rectangle(), 15 | perimeter_triangle(), 16 | ok. 17 | 18 | area_circle() -> 19 | Circle = #circle{radius = 10}, 20 | Area = math:pi() * 100, 21 | Area = shapes:area(Circle). 22 | 23 | area_rectangle() -> 24 | Rectangle = #rectangle{length = 10, width = 5}, 25 | 50 = shapes:area(Rectangle). 26 | 27 | area_triangle() -> 28 | Triangle = #triangle{a = 3, b = 4, c = 5}, 29 | 6.0 = shapes:area(Triangle). 30 | 31 | perimeter_circle() -> 32 | Circle = #circle{radius = 10}, 33 | Perimeter = math:pi() * 20, 34 | Perimeter = shapes:perimeter(Circle). 35 | 36 | perimeter_rectangle() -> 37 | Rectangle = #rectangle{length = 10, width = 5}, 38 | 30 = shapes:perimeter(Rectangle). 39 | 40 | perimeter_triangle() -> 41 | Triangle = #triangle{a = 5, b = 6, c = 7}, 42 | 18 = shapes:perimeter(Triangle). 43 | -------------------------------------------------------------------------------- /src/show_eval_test.erl: -------------------------------------------------------------------------------- 1 | % 2 | % F.Cesarini & S.Thomson, Erlang Programming, p.170. 3 | % Exercise 7-6: Parametrized Macros 4 | % 5 | % First compile without 'show' option 6 | % $ erlc show_eval_test.erl 7 | % then 8 | % $ erlc -Dshow show_eval_test.erl 9 | % 10 | -module(show_eval_test). 11 | -export([test/0]). 12 | -include("Shapes.hrl"). 13 | -include("Debug.hrl"). 14 | 15 | test() -> 16 | test_show_eval(), 17 | ok. 18 | 19 | test_show_eval() -> 20 | Area = ?SHOW_EVAL(shapes:area(#circle{radius = 10})), 21 | io:format("Printining from test: ~p~n", [Area]). 22 | -------------------------------------------------------------------------------- /src/stats_handler.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.138. 3 | %% Exercise 5-4: Event Statistics 4 | %% 5 | %% 1> event_manager:start(alarm, [{stats_handler, []}]). 6 | %% ok 7 | %% 2> event_manager:send_event(alarm, {event, 156, link_up}). 8 | %% ok 9 | %% 3> event_manager:send_event(alarm, {raise_alarm, 10, cabinet_open}). 10 | %% ok 11 | %% 4> event_manager:send_event(alarm, {clear_alarm, 10, cabinet_open}). 12 | %% ok 13 | %% 5> event_manager:send_event(alarm, {event, 156, link_up}). 14 | %% ok 15 | %% 6> event_manager:get_data(alarm, stats_handler). 16 | %% {data,[{{clear_alarm,10},1}, 17 | %% {{raise_alarm,10},1}, 18 | %% {{event,156},2}]} 19 | %% 7> event_manager:stop(alarm). 20 | %% 21 | -module(stats_handler). 22 | -export([init/1, terminate/1, handle_event/2]). 23 | 24 | init(InitStats) -> InitStats. 25 | 26 | terminate(Stats) -> {data, Stats}. 27 | 28 | handle_event({Type, Id, _Description}, Stats) -> 29 | case lists:keysearch({Type, Id}, 1, Stats) of 30 | false -> 31 | [{{Type, Id}, 1}|Stats]; 32 | {value, {{Type, Id}, Count}} -> 33 | lists:keyreplace({Type, Id}, 1, Stats, {{Type, Id}, Count + 1}) 34 | end. 35 | -------------------------------------------------------------------------------- /src/turnstile.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% http://www.objectmentor.com/resources/articles/umlfsm.pdf 3 | %% 4 | %% See also: turnstile2.erl 5 | %% 6 | %% 1> turnstile:start(). 7 | %% 2> turnstile:coin(). 8 | %% 3> turnstile:coin(). 9 | %% 4> turnstile:pass(). 10 | %% 5> turnstile:pass(). 11 | %% 12 | -module(turnstile). 13 | -export([start/0]). 14 | -export([coin/0, pass/0]). 15 | -export([init/0]). 16 | 17 | start() -> register(turnstile, spawn(?MODULE, init, [])). 18 | 19 | % Initial state 20 | 21 | init() -> locked(). 22 | 23 | % Events 24 | 25 | coin() -> turnstile ! coin. 26 | pass() -> turnstile ! pass. 27 | 28 | % States and transitions 29 | 30 | locked() -> 31 | receive 32 | pass -> 33 | alarm(), 34 | locked(); 35 | coin -> 36 | unlock(), 37 | unlocked() 38 | end. 39 | 40 | unlocked() -> 41 | receive 42 | pass -> 43 | lock(), 44 | locked(); 45 | coin -> 46 | thankyou(), 47 | unlocked() 48 | end. 49 | 50 | % Actions 51 | 52 | alarm() -> io:format("You shall not pass!~n"). 53 | unlock() -> io:format("Unlocking...~n"). 54 | lock() -> io:format("Locking...~n"). 55 | thankyou() -> io:format("Thank you for donation~n"). 56 | -------------------------------------------------------------------------------- /src/turnstile2.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% Erlang Programming, p.125. 3 | %% FSM Pattern. 4 | %% 5 | %% 1> turnstile2:start(). 6 | %% 2> turnstile2:coin(). 7 | %% 3> turnstile2:coin(). 8 | %% 4> turnstile2:pass(). 9 | %% 5> turnstile2:pass(). 10 | %% 6> turnstile2:stop(). 11 | %% 12 | -module(turnstile2). 13 | -export([start/0, stop/0]). 14 | -export([coin/0, pass/0]). 15 | -export([init/0]). 16 | 17 | % Start/stop functions 18 | 19 | start() -> register(turnstile2, spawn(?MODULE, init, [])). 20 | stop() -> turnstile2 ! stop. 21 | 22 | % Initial state 23 | 24 | init() -> locked(). 25 | 26 | % Events 27 | 28 | coin() -> turnstile2 ! coin. 29 | pass() -> turnstile2 ! pass. 30 | 31 | % States and transitions 32 | 33 | locked() -> 34 | receive 35 | pass -> 36 | alarm(), 37 | locked(); 38 | coin -> 39 | unlock(), 40 | unlocked(); 41 | stop -> 42 | ok 43 | end. 44 | 45 | unlocked() -> 46 | receive 47 | pass -> 48 | lock(), 49 | locked(); 50 | coin -> 51 | thankyou(), 52 | unlocked(); 53 | stop -> 54 | ok 55 | end. 56 | 57 | % Actions 58 | 59 | alarm() -> io:format("You shall not pass!~n"). 60 | unlock() -> io:format("Unlocking...~n"). 61 | lock() -> io:format("Locking...~n"). 62 | thankyou() -> io:format("Thank you for donation~n"). 63 | -------------------------------------------------------------------------------- /src/turnstile_gen.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% See also: turnstile.erl 3 | %% 4 | -module(turnstile_gen). 5 | -export([start/0, stop/0]). 6 | -export([coin/0, pass/0]). 7 | -export([init/1, callback_mode/0, locked/3, unlocked/3]). 8 | -behaviour(gen_statem). 9 | 10 | % Start/stop functions 11 | 12 | start() -> 13 | gen_statem:start_link({local, ?MODULE}, ?MODULE, [], []). 14 | 15 | stop() -> 16 | gen_statem:stop(?MODULE). 17 | 18 | % Functional interface 19 | 20 | coin() -> 21 | gen_statem:call(?MODULE, coin). 22 | 23 | pass() -> 24 | gen_statem:call(?MODULE, pass). 25 | 26 | % Callback functions 27 | 28 | init(_) -> 29 | {ok, locked, []}. 30 | 31 | callback_mode() -> 32 | state_functions. 33 | 34 | locked({call, From}, coin, _) -> 35 | do_unlock(), 36 | {next_state, unlocked, [], {reply, From, unlocked}}; 37 | 38 | locked({call, From}, pass, _) -> 39 | do_alarm(), 40 | {keep_state, [], {reply, From, locked}}. 41 | 42 | unlocked({call, From}, coin, _) -> 43 | do_thankyou(), 44 | {keep_state, [], {reply, From, unlocked}}; 45 | 46 | unlocked({call, From}, pass, _) -> 47 | do_lock(), 48 | {next_state, locked, [], {reply, From, locked}}. 49 | 50 | % Actions 51 | 52 | do_alarm() -> io:format("You shall not pass!~n"). 53 | do_unlock() -> io:format("Unlocking...~n"). 54 | do_lock() -> io:format("Locking...~n"). 55 | do_thankyou() -> io:format("Thank you for your donation~n"). 56 | -------------------------------------------------------------------------------- /tcp/src/echo_active.erl: -------------------------------------------------------------------------------- 1 | % 2 | % http://forums.pragprog.com/forums/27/topics/390 3 | % 4 | % 1> echo_active:start(4321). 5 | % 6 | % telnet localhost 4321 7 | % 8 | -module(echo_active). 9 | -author('Alain O\'Dea'). 10 | -export([start/1]). 11 | 12 | -define(TCP_OPTIONS, [list, {packet, line}, {reuseaddr, true}, {active, true}]). 13 | 14 | start(Port) -> 15 | {ok, Listen} = gen_tcp:listen(Port, ?TCP_OPTIONS), 16 | spawn(fun() -> par_connect(Listen) end). 17 | 18 | par_connect(Listen) -> 19 | {ok, Socket} = gen_tcp:accept(Listen), 20 | spawn(fun() -> par_connect(Listen) end), 21 | io:format("Client connected~n"), 22 | echo(Socket). 23 | 24 | echo(Socket) -> 25 | receive 26 | {tcp, Socket, Line} -> 27 | gen_tcp:send(Socket, Line), 28 | echo(Socket); 29 | {tcp_closed, Socket} -> 30 | io:format("Client disconnected~n") 31 | end. 32 | -------------------------------------------------------------------------------- /tcp/src/echo_passive.erl: -------------------------------------------------------------------------------- 1 | % 2 | % http://20bits.com/articles/erlang-a-generalized-tcp-server 3 | % 4 | % 1> echo_passive:start(1234). 5 | % 6 | % telnet localhost 1234 7 | % 8 | -module(echo_passive). 9 | -author('Jesse E.I. Farmer '). 10 | -export([start/1, listen/1]). 11 | 12 | -define(TCP_OPTIONS, [{packet, 0}, {active, false}, {reuseaddr, true}]). 13 | 14 | start(Port) -> 15 | spawn(?MODULE, listen, [Port]). 16 | 17 | listen(Port) -> 18 | {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS), 19 | accept(LSocket). 20 | 21 | % Wait for incoming connections and spawn the echo loop when we get one. 22 | accept(LSocket) -> 23 | {ok, Socket} = gen_tcp:accept(LSocket), 24 | spawn(fun() -> loop(Socket) end), 25 | io:format("Client connected~n"), 26 | accept(LSocket). 27 | 28 | % Echo back whatever data we receive on Socket. 29 | loop(Socket) -> 30 | case gen_tcp:recv(Socket, 0) of 31 | {ok, Data} -> 32 | gen_tcp:send(Socket, Data), 33 | loop(Socket); 34 | {error, closed} -> 35 | io:format("Client disconnected~n") 36 | end. 37 | -------------------------------------------------------------------------------- /tcp/src/http_proxy.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.334. 3 | %% Exercise 15-2: HTTP proxy 4 | %% 5 | %% Based on http_snoop.erl 6 | %% 7 | -module(http_proxy). 8 | -export([start/1, listen/1]). 9 | 10 | -define(TCP_OPTIONS, [{packet, 0}, {active, false}, {reuseaddr, true}]). 11 | 12 | start(Port) -> 13 | inets:start(), 14 | spawn(?MODULE, listen, [Port]). 15 | 16 | listen(Port) -> 17 | {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS), 18 | accept(LSocket). 19 | 20 | accept(LSocket) -> 21 | {ok, Socket} = gen_tcp:accept(LSocket), 22 | spawn(fun() -> loop(Socket) end), 23 | accept(LSocket). 24 | 25 | loop(Socket) -> 26 | case gen_tcp:recv(Socket, 0) of 27 | {ok, Data} -> 28 | io:format("~p~n", [Data]), 29 | gen_tcp:send(Socket, get_response(get_request_url(Data))), 30 | gen_tcp:close(Socket), 31 | loop(Socket); 32 | {error, closed} -> 33 | ok 34 | end. 35 | 36 | get_request_url(Request) -> 37 | {match, [Url]} = re:run(Request, "GET (.+?) HTTP", [{capture, [1], list}]), 38 | Url. 39 | 40 | get_response(RequestUrl) -> 41 | {ok, {_Http, _Headers, Body}} = http:request(RequestUrl), 42 | Body. 43 | -------------------------------------------------------------------------------- /tcp/src/http_snoop.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.334. 3 | %% Exercise 15-1: Snooping HTTP request 4 | %% 5 | %% Based on echo.erl 6 | %% 7 | -module(http_snoop). 8 | -export([start/1, listen/1]). 9 | 10 | -define(TCP_OPTIONS, [{packet, 0}, {active, false}, {reuseaddr, true}]). 11 | 12 | start(Port) -> 13 | spawn(?MODULE, listen, [Port]). 14 | 15 | listen(Port) -> 16 | {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS), 17 | accept(LSocket). 18 | 19 | accept(LSocket) -> 20 | {ok, Socket} = gen_tcp:accept(LSocket), 21 | spawn(fun() -> loop(Socket) end), 22 | accept(LSocket). 23 | 24 | loop(Socket) -> 25 | case gen_tcp:recv(Socket, 0) of 26 | {ok, Data} -> 27 | io:format("Data: ~p~n", [Data]), 28 | gen_tcp:send(Socket, Data), 29 | gen_tcp:close(Socket), 30 | loop(Socket); 31 | {error, closed} -> 32 | ok 33 | end. 34 | -------------------------------------------------------------------------------- /tcp/src/peer.erl: -------------------------------------------------------------------------------- 1 | %% 2 | %% F.Cesarini & S.Thomson, Erlang Programming, p.334. 3 | %% Exercise 15-3: Peer to peer 4 | %% 5 | %% 1> peer:start(). 6 | %% 7 | %% 1> peer:connect("localhost"). 8 | %% ok 9 | %% 2> peer:send("Hello server"). 10 | %% ok 11 | %% Client received reply: "Reply: Hello server" 12 | %% 13 | -module(peer). 14 | -export([start/0, connect/1, send/1]). 15 | 16 | -define(TCP_OPTIONS, [binary, {packet, 4}, {active, true}, {reuseaddr, true}]). 17 | 18 | % Server 19 | 20 | start() -> 21 | {ok, Listen} = gen_tcp:listen(1234, ?TCP_OPTIONS), 22 | seq_loop(Listen). 23 | 24 | seq_loop(Listen) -> 25 | {ok, Socket} = gen_tcp:accept(Listen), 26 | loop(Socket), 27 | seq_loop(Listen). 28 | 29 | loop(Socket) -> 30 | receive 31 | {tcp, Socket, Bin} -> 32 | Request = binary_to_term(Bin), 33 | io:format("Server received request: ~p~n", [Request]), 34 | Reply = "Reply: " ++ Request, 35 | gen_tcp:send(Socket, term_to_binary(Reply)), 36 | loop(Socket); 37 | {tcp_closed, Socket} -> 38 | io:format("Server socket closed~n") 39 | end. 40 | 41 | % Client 42 | 43 | connect(IpAddress) -> 44 | register(?MODULE, spawn_link(fun() -> connect_wait(IpAddress) end)), 45 | ok. 46 | 47 | connect_wait(IpAddress) -> 48 | {ok, Socket} = gen_tcp:connect(IpAddress, 1234, [binary, {packet, 4}]), 49 | wait(Socket). 50 | 51 | wait(Socket) -> 52 | receive 53 | {request, Request} -> 54 | ok = gen_tcp:send(Socket, term_to_binary(Request)), 55 | wait(Socket); 56 | {tcp, Socket, Bin} -> 57 | Reply = binary_to_term(Bin), 58 | io:format("Client received reply: ~p~n", [Reply]), 59 | wait(Socket); 60 | {tcp_closed, Socket} -> 61 | io:format("Client socket closed~n") 62 | end. 63 | 64 | send(Request) -> 65 | ?MODULE ! {request, Request}, 66 | ok. 67 | -------------------------------------------------------------------------------- /test/ets_tests.erl: -------------------------------------------------------------------------------- 1 | -module(ets_tests). 2 | -include_lib("eunit/include/eunit.hrl"). 3 | 4 | setup() -> 5 | ets:new(countries, [bag,named_table]), 6 | ets:insert(countries, {yves,france,cook}), 7 | ets:insert(countries, {sean,ireland,bartender}), 8 | ets:insert(countries, {marco,italy,cook}), 9 | ets:insert(countries, {chris,ireland,tester}). 10 | 11 | teardown(_) -> 12 | ets:delete(countries). 13 | 14 | match_test_() -> 15 | {foreach, 16 | fun setup/0, 17 | fun teardown/1, 18 | [ 19 | fun no_match_after_elements_are_deleted/0, 20 | fun match_first_element_with_index_2/0, 21 | fun match_elements_but_dont_capture/0 22 | ] 23 | }. 24 | 25 | no_match_after_elements_are_deleted() -> 26 | fun() -> 27 | ets:match_delete(countries, {'_',ireland,'_'}), 28 | ?assertEqual([], ets:match_object(countries, {'_',ireland,'_'})) 29 | end. 30 | 31 | match_first_element_with_index_2() -> 32 | ?assertEqual([[sean],[chris]], ets:match(countries, {'$2',ireland,'_'})). 33 | 34 | match_elements_but_dont_capture() -> 35 | ?assertEqual([[],[]], ets:match(countries, {'_',ireland,'_'})). 36 | -------------------------------------------------------------------------------- /test/regex_tests.erl: -------------------------------------------------------------------------------- 1 | % 2 | % http://www.erlang.org/eeps/eep-0011.html 3 | % 4 | -module(regex_tests). 5 | -include_lib("eunit/include/eunit.hrl"). 6 | 7 | -define(BookPunctuation, "(\\ |\\,|\\.|\\;|\\:|\\t|\\n|\\(|\\))+"). 8 | -define(Punctuation, "(\\s|,|\\.|;|:|\\(|\\))+"). 9 | 10 | space_is_not_escaped_test() -> 11 | ?assertEqual(["first", "second"], re:split("first second", " ", [{return, list}])). 12 | 13 | comma_is_not_escaped_test() -> 14 | ?assertEqual(["first", "second"], re:split("first,second", ",", [{return, list}])). 15 | 16 | dot_is_escaped_test() -> 17 | ?assertEqual(["first", "second"], re:split("first.second", "\\.", [{return, list}])). 18 | 19 | semicolumn_is_not_escaped_test() -> 20 | ?assertEqual(["first", "second"], re:split("first;second", ";", [{return, list}])). 21 | 22 | column_is_not_escaped_test() -> 23 | ?assertEqual(["first", "second"], re:split("first:second", ":", [{return, list}])). 24 | 25 | 26 | whitespace_character_test() -> 27 | ?assertEqual(["first", "second", "third", "forth"], re:split("first second\tthird\nforth", "\\s", [{return, list}])). 28 | 29 | %punctuation_test() -> 30 | % ?assertEqual(["a", "b", "c", "d", "e", "f", "g"], re:split("a b,c.d;e:f(g)", ?Punctuation, [{return, list}, trim])). 31 | 32 | capturing_region_test() -> 33 | ?assertEqual({match, [{3,4}]}, re:run("ABCabcdABC", "(abcd)", [{capture, [1]}])). 34 | 35 | capturing_text_single_test() -> 36 | ?assertEqual({match, ["abcd"]}, re:run("ABCabcdABC", "(abcd)", [{capture, [1], list}])). 37 | 38 | capturing_text_multiple_test() -> 39 | ?assertEqual({match, ["ABC", "ABC"]}, re:run("ABCabcdABC", "(ABC)", [{capture, all, list}])). 40 | 41 | % Deprecated 42 | 43 | %book_punctuation_test() -> 44 | % ?assertEqual({ok, ["first", "second"]}, regexp:split("first,second", ?BookPunctuation)). 45 | 46 | %my_punctuation_test() -> 47 | % ?assertEqual({ok, ["first", "second"]}, regexp:split("first,second", ?Punctuation)). 48 | --------------------------------------------------------------------------------