Skip to content

Commit c59f6f4

Browse files
committed
Complete exercise 23.3
1 parent e40e21b commit c59f6f4

12 files changed

+574
-0
lines changed

‎chapter_23/exercise_3/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
_build

‎chapter_23/exercise_3/rebar.config‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{deps, []}.

‎chapter_23/exercise_3/rebar.lock‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
[].
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
%% ---
2+
%% Excerpted from "Programming Erlang, Second Edition",
3+
%% published by The Pragmatic Bookshelf.
4+
%% Copyrights apply to this code. It may not be used to create training material,
5+
%% courses, books, articles, and the like. Contact us if you are in doubt.
6+
%% We make no guarantees that this code is fit for any purpose.
7+
%% Visit http://www.pragmaticprogrammer.com/titles/jaerlang2 for more book information.
8+
%%---
9+
-module(lib_lin).
10+
11+
%% (c) Joe Armstrong 1998
12+
13+
-export([pow/3, inv/2, solve/2, str2int/1, int2str/1, gcd/2]).
14+
15+
%% pow(A, B, M) => (A^B) mod M
16+
%% examples pow(9726,3533,11413) = 5761
17+
%% pow(5971,6597,11413) = 9726
18+
19+
pow(A, 1, M) ->
20+
AremM;
21+
pow(A, 2, M) ->
22+
A*AremM;
23+
pow(A, B, M) ->
24+
B1=Bdiv2,
25+
B2=B-B1,
26+
%% B2 = B1 or B1+1
27+
P=pow(A, B1, M),
28+
caseB2of
29+
B1 -> (P*P) remM;
30+
_ -> (P*P*A) remM
31+
end.
32+
33+
%% inv(A, B) = C | no_inverse
34+
%% computes C such that
35+
%% A*C mod B = 1
36+
%% computes A^-1 mod B
37+
%% examples inv(28, 75) = 67.
38+
%% inv(3533, 11200) = 6597
39+
%% inv(6597, 11200) = 3533
40+
41+
inv(A, B) ->
42+
casesolve(A, B) of
43+
{X, _} ->
44+
ifX<0 -> X+B;
45+
true -> X
46+
end;
47+
_ ->
48+
no_inverse
49+
end.
50+
51+
%% solve(A, B) =>{X, Y} | insoluble
52+
%% solve the linear congruence
53+
%% A * X - B * Y = 1
54+
55+
solve(A, B) ->
56+
casecatchs(A,B) of
57+
insoluble -> insoluble;
58+
{X, Y} ->
59+
caseA*X-B*Yof
60+
1 ->{X, Y};
61+
_Other -> error
62+
end
63+
end.
64+
65+
s(_, 0) ->throw(insoluble);
66+
s(_, 1) ->{0, -1};
67+
s(_, -1) ->{0, 1};
68+
s(A, B) ->
69+
K1=AdivB,
70+
K2=A-K1*B,
71+
{Tmp, X} =s(B, -K2),
72+
{X, K1*X-Tmp}.
73+
74+
75+
76+
%% converts a string to a base 256 integer
77+
%% converts a base 256 integer to a string
78+
79+
str2int(Str) ->str2int(Str, 0).
80+
81+
str2int([H|T], N) ->str2int(T, N*256+H);
82+
str2int([], N) ->N.
83+
84+
int2str(N) ->int2str(N, []).
85+
86+
int2str(N, L) whenN=<0->L;
87+
int2str(N, L) ->
88+
N1=Ndiv256,
89+
H=N-N1*256,
90+
int2str(N1, [H|L]).
91+
92+
%% greatest common devisor
93+
94+
gcd(A, B) whenA<B->gcd(B, A);
95+
gcd(A, 0) ->A;
96+
gcd(A, B) ->gcd(B, AremB).
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
%% ---
2+
%% Excerpted from "Programming Erlang, Second Edition",
3+
%% published by The Pragmatic Bookshelf.
4+
%% Copyrights apply to this code. It may not be used to create training material,
5+
%% courses, books, articles, and the like. Contact us if you are in doubt.
6+
%% We make no guarantees that this code is fit for any purpose.
7+
%% Visit http://www.pragmaticprogrammer.com/titles/jaerlang2 for more book information.
8+
%%---
9+
-module(lib_primes).
10+
-export([make_prime/1, is_prime/1, make_random_int/1]).
11+
12+
make_prime(1) ->%% <label id="make_primes1" />
13+
lists:nth(random:uniform(4), [2,3,5,7]);
14+
make_prime(K) whenK>0->%% <label id="make_primes2" />
15+
new_seed(),
16+
N=make_random_int(K),
17+
ifN>3 ->
18+
io:format("Generating a ~w digit prime ",[K]),
19+
MaxTries=N-3,
20+
P1=make_prime(MaxTries, N+1),
21+
io:format("~n",[]),
22+
P1;
23+
true ->
24+
make_prime(K)
25+
end. %% <label id="make_primes3" />
26+
27+
make_prime(0, _) ->%% <label id="prime_loop1" />
28+
exit(impossible);
29+
make_prime(K, P) ->
30+
io:format(".",[]),
31+
caseis_prime(P) of
32+
true -> P;
33+
false -> make_prime(K-1, P+1)
34+
end. %% <label id="prime_loop2" />
35+
36+
is_prime(D) whenD<10->
37+
lists:member(D, [2,3,5,7]);
38+
is_prime(D) ->
39+
new_seed(),
40+
is_prime(D, 100).
41+
42+
is_prime(D, Ntests) ->
43+
N=length(integer_to_list(D)) -1,
44+
is_prime(Ntests, D, N).
45+
46+
is_prime(0, _, _) ->true; %% <label id="is_prime_1" />
47+
is_prime(Ntest, N, Len) ->%% <label id="is_prime_2" />
48+
K=random:uniform(Len),
49+
%% A is a random number less than K
50+
A=make_random_int(K),
51+
if
52+
A<N ->
53+
caselib_lin:pow(A,N,N) of
54+
A -> is_prime(Ntest-1,N,Len);
55+
_ -> false
56+
end;
57+
true ->
58+
is_prime(Ntest, N, Len)
59+
end. %% <label id="is_prime_3" />
60+
61+
%% make_random_int(N) -> a random integer with N digits.
62+
make_random_int(N) ->new_seed(), make_random_int(N, 0). %% <label id="make_ran_int1" />
63+
64+
make_random_int(0, D) ->D;
65+
make_random_int(N, D) ->
66+
make_random_int(N-1, D*10+ (random:uniform(10)-1)). %% <label id="make_ran_int2" />
67+
68+
69+
70+
%END:make_ran_int
71+
72+
new_seed() ->
73+
{_,_,X} =erlang:now(),
74+
{H,M,S} =time(),
75+
H1=H*Xrem32767,
76+
M1=M*Xrem32767,
77+
S1=S*Xrem32767,
78+
put(random_seed,{H1,M1,S1}).
79+
80+
81+
82+
83+
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
%%%-------------------------------------------------------------------
2+
%%% @author trevor
3+
%%% @copyright (C) 2020, trevor
4+
%%% @doc
5+
%%% The queue server to handle queuing sending individual
6+
%%% @end
7+
%%%-------------------------------------------------------------------
8+
-module(prime_tester_load_balancer).
9+
10+
-behaviour(gen_server).
11+
12+
%% API
13+
-export([start_link/1, is_prime/1]).
14+
15+
%% gen_server callbacks
16+
-export([init/1,
17+
handle_call/3,
18+
handle_cast/2,
19+
handle_info/2,
20+
terminate/2,
21+
code_change/3]).
22+
23+
-define(SERVER, ?MODULE).
24+
25+
-record(state,{limit=0,
26+
free_pids=[],
27+
jobs=#{},
28+
queue=queue:new()}).
29+
30+
%%%===================================================================
31+
%%% API
32+
%%%===================================================================
33+
34+
%%--------------------------------------------------------------------
35+
%% @doc
36+
%% Starts the server
37+
%%
38+
%% @end
39+
%%--------------------------------------------------------------------
40+
-specstart_link(Limit::integer()) ->{ok, Pid::pid()} | ignore |{error, Error::any()}.
41+
42+
start_link(Limit) whenis_integer(Limit) ->
43+
gen_server:start_link({local, ?SERVER}, ?MODULE,{Limit}, []).
44+
45+
-specis_prime(Number::integer()) ->any().
46+
47+
is_prime(Number) ->
48+
gen_server:call(?SERVER,{is_prime, Number}).
49+
50+
%%%===================================================================
51+
%%% gen_server callbacks
52+
%%%===================================================================
53+
54+
init({Limit}) ->
55+
% Start up all workers
56+
Pids=lists:map(fun(_Num) ->
57+
% Start the worker
58+
{ok, Pid} =supervisor:start_child(prime_tester_worker_sup, []),
59+
_Ref=erlang:monitor(process, Pid),
60+
Pid
61+
end, lists:seq(1, Limit)),
62+
63+
% Store pids in state
64+
{ok, #state{free_pids=Pids}}.
65+
66+
handle_call({is_prime, Number}, From, State=#state{free_pids=[], queue=Q}) ->
67+
% If we've already hit our limit for workers queue the request
68+
{noreply, State#state{queue=queue:in({From, Number}, Q)}};
69+
handle_call({is_prime, Number}, From, State=#state{free_pids=[Pid|Pids], jobs=R}) ->
70+
% If we still haven't hit our limit for workers, give the worker the new job
71+
ok=prime_tester_worker:is_prime(Pid, Number),
72+
73+
% We will reply when the worker replies to us
74+
{noreply, State#state{free_pids=Pids, jobs=maps:put(Pid,{From, Number}, R)}};
75+
handle_call(_Request, _From, State) ->
76+
Reply=ok,
77+
{reply, Reply, State}.
78+
79+
handle_cast({worker_result, Pid, Result}, State=#state{free_pids=FreePids, jobs=Jobs, queue=Queue}) ->
80+
% Lookup client pid in map
81+
casemaps:is_key(Pid, Jobs) of
82+
true ->
83+
% Send the reply back to the original `From` pid
84+
{From, _Number} =maps:get(Pid, Jobs),
85+
gen_server:reply(From,{ok, Result}),
86+
87+
% Start the next job
88+
casequeue:out(Queue) of
89+
{{value,{From, Number}}, NewQueue} ->
90+
% Put the old pid back in the list of pids and get the new one
91+
[NewPid|RestPids] =FreePids,
92+
NewFreePids=RestPids++ [Pid],
93+
94+
% Start the new job
95+
ok=prime_tester_worker:is_prime(NewPid, Number),
96+
NewJobs=maps:put(NewPid,{From, Number}, maps:remove(Pid, Jobs)),
97+
98+
{noreply, State#state{free_pids=NewFreePids, jobs=NewJobs, queue=NewQueue}};
99+
{empty, _} ->
100+
% If empty no change
101+
{noreply, State#state{free_pids=FreePids++ [Pid], jobs=maps:remove(Pid, Jobs)}}
102+
end;
103+
false ->
104+
% If we don't find a pid in a map ignore this message
105+
{noreply, State#state{jobs=maps:remove(Pid, Jobs)}}
106+
end;
107+
handle_cast(_Msg, State) ->
108+
{noreply, State}.
109+
110+
% Handle getting a down message from a finished worker
111+
handle_info({'DOWN', _Ref, process, Pid, _}, State=#state{jobs=Pids}) ->
112+
casemaps:is_key(Pid, Pids) of
113+
true ->
114+
handle_down_worker(Pid, State);
115+
false -> %% Not our responsibility
116+
{noreply, State}
117+
end;
118+
handle_info(_Info, State) ->
119+
{noreply, State}.
120+
121+
terminate(_Reason, _State) ->
122+
ok.
123+
124+
code_change(_OldVsn, State, _Extra) ->
125+
{ok, State}.
126+
127+
%%%===================================================================
128+
%%% Internal functions
129+
%%%===================================================================
130+
131+
handle_down_worker(Pid, State=#state{limit=Limit, jobs=Pids, queue=Queue}) ->
132+
io:format("got worker down"),
133+
casequeue:out(Queue) of
134+
{{value,{From, Number}}, NewQueue} ->
135+
{ok, Pid} =supervisor:start_child(prime_tester_worker_sup, [Number]),
136+
NewPid=erlang:monitor(process, Pid),
137+
NewPids=gb_sets:insert(NewPid, maps:remove(Pid,Pids)),
138+
gen_server:reply(From,{ok, Pid}),
139+
{noreply, State#state{jobs=NewPids, queue=NewQueue}};
140+
{empty, _} ->
141+
% If nothing in the queue continue as before
142+
{noreply, State#state{limit=Limit+1, jobs=gb_sets:delete(Pid, Pids)}}
143+
end.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
-module(prime_tester_server_test).
2+
3+
-export([run/0]).
4+
5+
run() ->
6+
% Start the application
7+
ok=application:start(sellaprime),
8+
9+
CallPrimeTester=fun(Number) ->
10+
io:format("Calling prime server~n"),
11+
Result=prime_tester_server:is_prime(Number),
12+
io:format("Got a result from prime server: ~p~n", [Result])
13+
end,
14+
15+
% Queue some tasks
16+
spawn_monitor(fun() ->CallPrimeTester(7) end),
17+
spawn_monitor(fun() ->CallPrimeTester(8) end),
18+
spawn_monitor(fun() ->CallPrimeTester(9) end),
19+
20+
% Stop the application
21+
%ok = application:stop(sellaprime).
22+
ok.

0 commit comments

Comments
(0)