Permalink
Browse files

first naive brute-force implemenation

  • Loading branch information...
1 parent ee02142 commit 6c248f2548c58b1803d726412f70980e487511f7 @martinrehfeld committed May 6, 2012
No changes.
No changes.
File renamed without changes.
File renamed without changes.
@@ -1,8 +1,8 @@
% {erl_opts, [warnings_as_errors, debug_info]}.
{erl_opts, [debug_info]}.
-{require_otp_vsn, "R14"}.
+{require_otp_vsn, "R15"}.
{deps, [
- {meck, "0.7.1", {git, "git://github.com/eproxus/meck.git", {tag, "916844d6d1df6b"}}}
+ {meck, "0.7.1", {git, "git://github.com/eproxus/meck.git", {tag, "0.7.1"}}}
]}.
{clean_files, ["ebin/*.beam"]}.
File renamed without changes.
@@ -0,0 +1,109 @@
+-module(cd_solver).
+
+%% API
+-export([solutions/2]).
+
+%% only exported for eunit
+-export([add/2, subtract/2, multiply/2, divide/2, available_indexes/2]).
+
+-define(OPERATORS, [{'+', fun add/2},
+ {'-', fun subtract/2},
+ {'*', fun multiply/2},
+ {'/', fun divide/2}]).
+
+-record(vertex, {result, used}).
+-record(edge, {operator, operand}).
+
+solutions(Target, Numbers) ->
+ {G, Root} = build_graph(Numbers),
+ best_solutions(G, Root, Target).
+
+build_graph(Numbers) ->
+ G = digraph:new([acyclic]),
+ Used = 0,
+ Root = digraph:add_vertex(G, #vertex{result = 0, used = Used}),
+ {add_next_level(G, Root, available_indexes(Used, length(Numbers)), Numbers),
+ Root}.
+
+add_next_level(G, VStart, AvailableIndexes, Numbers) ->
+ lists:foldl(fun(Index, G1) ->
+ add_operations(G1, VStart, Index, Numbers, ?OPERATORS)
+ end, G, AvailableIndexes).
+
+add_operations(G, VStart, Index, Numbers, Operators) ->
+ lists:foldl(fun(Operator, G1) ->
+ add_compute_step(G1, VStart, Index, Numbers, Operator)
+ end, G, Operators).
+
+add_compute_step(G, #vertex{result=N1,used=Bitmap0}=VStart, Index, Numbers, Operator) ->
+ N2 = lists:nth(Index, Numbers),
+ {OperatorName, OperatorFun} = Operator,
+
+ case OperatorFun(N1, N2) of
+ no_op -> G;
+ not_allowed -> G;
+
+ Result ->
+ Bitmap1 = Bitmap0 bor (1 bsl Index),
+ Vertex= #vertex{result = Result, used = Bitmap1},
+ VNew = digraph:add_vertex(G, Vertex),
+ digraph:add_edge(G, VStart, VNew, #edge{operator = OperatorName, operand = N2}),
+ %% error_logger:info_msg("New Op ~p~n",
+ %% [digraph:edge(G, lists:nth(1, digraph:out_edges(G, VStart)))]),
+ add_next_level(G, VNew, available_indexes(Bitmap1, length(Numbers)),
+ Numbers)
+ end.
+
+available_indexes(Bitmap, Size) ->
+ lists:filter(fun(Index) ->
+ Mask = bnot (1 bsl Index),
+ Bitmap bor Mask =:= Mask
+ end, lists:seq(1, Size)).
+
+best_solutions(G, Root, Target) ->
+ Vertices = digraph:vertices(G),
+ Deltas =
+ lists:map(fun(V) ->
+ {abs(V#vertex.result - Target), V}
+ end, Vertices),
+ OrderedDeltas =
+ lists:sort(fun(A, B) ->
+ {Delta1, _Vertex1} = A,
+ {Delta2, _Vertex2} = B,
+ Delta1 =< Delta2
+ end, Deltas),
+ {_BestDelta, BestVertex} = hd(OrderedDeltas),
+ Solutions = [{BestVertex#vertex.result, calculations(G, Root, BestVertex)}],
+ error_logger:info_msg("Found best solution (out of ~p unique results):~n~p~n",
+ [length(Vertices), Solutions]),
+ Solutions.
+
+calculations(G, Root, BestVertex) ->
+ calculations(G, Root, BestVertex, []).
+
+calculations(G, Root, EndVertex, Operations) ->
+ InEdges = digraph:in_edges(G, EndVertex),
+ E = hd(InEdges),
+ {_, SourceVertex, _, Operation} = digraph:edge(G, E),
+ case SourceVertex of
+ Root -> [Operation|Operations];
+ _ -> calculations(G, Root, SourceVertex, [Operation|Operations])
+ end.
+
+%%
+%% Operators
+%%
+
+add(N1, N2) when N2 =/= 0 -> N1 + N2;
+add(_N1, _N2) -> no_op.
+
+subtract(N1, N2) when N1 < N2 -> not_allowed;
+subtract(_N1, N2) when N2 =:= 0 -> no_op;
+subtract(N1, N2) when N1 >= N2 -> N1 - N2.
+
+multiply(N1, N2) when N2 =/= 1 -> N1 * N2;
+multiply(_N1, _N2) -> no_op.
+
+divide(N1, N2) when N1 rem N2 =/= 0 -> not_allowed;
+divide(_N1, N2) when N2 =:= 1 -> no_op;
+divide(N1, N2) when N1 rem N2 =:= 0 -> N1 div N2.
@@ -1,12 +1,11 @@
-{application, _template,
+{application, countdown,
[
- {description, ""},
+ {description, "Solver for the numbers round of the Countdown TV show"},
{vsn, "0.0.1"},
{registered, []},
{applications, [
kernel,
- stdlib,
- sasl
+ stdlib
]},
{env, []}
]}.
File renamed without changes.
@@ -0,0 +1,53 @@
+-module(cd_solver_tests).
+-include_lib("eunit/include/eunit.hrl").
+
+%%
+%% Macros for getting stacktraces on demand
+%%
+
+-define(TEST_START, try ok).
+-define(TEST_END,
+ ok
+ catch
+ Type:X ->
+ io:format("~p~n", [{Type, X, erlang:get_stacktrace()}]),
+ ?assert(unhandled_exception)
+ end).
+
+%%
+%% TESTS
+%%
+
+available_indexes_test() ->
+ ?assertEqual([], cd_solver:available_indexes(2,1)),
+ ?assertEqual([1], cd_solver:available_indexes(0,1)),
+ ?assertEqual([2], cd_solver:available_indexes(2,2)),
+ ?assertEqual([3], cd_solver:available_indexes(6,3)).
+
+add_test() ->
+ ?assertEqual(no_op, cd_solver:add(1, 0)),
+ ?assertEqual(2, cd_solver:add(1, 1)).
+
+subtract_test() ->
+ ?assertEqual(no_op, cd_solver:subtract(1, 0)),
+ ?assertEqual(not_allowed, cd_solver:subtract(1, 2)),
+ ?assertEqual(0, cd_solver:subtract(1, 1)).
+
+multiply_test() ->
+ ?assertEqual(no_op, cd_solver:multiply(1, 1)),
+ ?assertEqual(2, cd_solver:multiply(1, 2)).
+
+divide_test() ->
+ ?assertEqual(no_op, cd_solver:divide(1, 1)),
+ ?assertEqual(not_allowed, cd_solver:divide(1, 2)),
+ ?assertEqual(1, cd_solver:divide(2, 2)).
+
+solver_test_() ->
+ %% Solutions: [{actual_result, [n1, op1, n2, op2, ...]}, ...]
+ {timeout, 600, fun() ->
+ Solutions1 = cd_solver:solutions(178, [100, 75, 3]),
+ %% Solutions2 = cd_solver:solutions(203, [50, 100, 4, 2, 2, 4]),
+ [?_assert(proplists:lookup(178, Solutions1) =/= none)
+ %% ,?_assert(proplists:lookup(203, Solutions2) =/= none)
+ ]
+ end}.
View
@@ -1,8 +1,7 @@
{sub_dirs, [
- "apps/*",
- "rel"
+ "apps/countdown"
]}.
% {erl_opts, [warnings_as_errors, debug_info]}.
{erl_opts, [debug_info]}.
-{require_otp_vsn, "R14"}.
+{require_otp_vsn, "R15"}.

0 comments on commit 6c248f2

Please sign in to comment.