From b857a78fce0f99dba69f194907a9eed6a7875fbe Mon Sep 17 00:00:00 2001 From: ajfAfg <56056962+ajfAfg@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:28:08 +0900 Subject: [PATCH 1/3] Add the flatten function like OCaml and its test --- src/core/my_lists.erl | 6 +++++- test/prop_my_lists.erl | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/core/my_lists.erl b/src/core/my_lists.erl index a645cba..831b9b9 100644 --- a/src/core/my_lists.erl +++ b/src/core/my_lists.erl @@ -1,6 +1,6 @@ -module(my_lists). --export([shuffle/1, sublist_randomly/1, power/1]). +-export([shuffle/1, sublist_randomly/1, power/1, flatten/1]). -spec shuffle(list()) -> list(). shuffle(List) -> [X || {_, X} <- lists:sort([{rand:uniform(), Y} || Y <- List])]. @@ -16,3 +16,7 @@ power(List) -> power_([], List, []). power_(Subset, [], Family) -> [Subset | Family]; power_(Subset, [Head | Rest], Family) -> power_(Subset, Rest, power_([Head | Subset], Rest, Family)). + +% NOTE: Unlike `lists:flatten/1`, this function removes only one level of the nested list. +-spec flatten([list()]) -> list(). +flatten(ListList) -> [X || List <- ListList, X <- List]. diff --git a/test/prop_my_lists.erl b/test/prop_my_lists.erl index 86eca30..a96567e 100644 --- a/test/prop_my_lists.erl +++ b/test/prop_my_lists.erl @@ -60,3 +60,17 @@ prop_power2() -> ?SUCHTHAT(L, list(random_type()), length(L) =< 15), lists:all(fun(List2) -> lists:sort(List2 ++ List -- List2) =:= lists:sort(List) end, my_lists:power(List))). + +%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% my_lists:flatten/1 %%% +%%%%%%%%%%%%%%%%%%%%%%%%%% +% NOTE: +% It is easy to give a different implementation, +% so I test by "Wording the specification differently". +prop_flatten1(doc) -> "Wording the specification differently". + +prop_flatten1() -> + ?FORALL(ListList, + list(list(random_type())), + lists:foldl(fun(List, Acc) -> List ++ Acc end, [], lists:reverse(ListList)) + =:= my_lists:flatten(ListList)). From 4252721ba3231cd65bdae795aa95752d015edc60 Mon Sep 17 00:00:00 2001 From: ajfAfg <56056962+ajfAfg@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:29:43 +0900 Subject: [PATCH 2/3] Fix `solve_in_exp_time_with_correctness/1` output result to satisfy all --- ..._local_minimum_vertex_splitters_solver.erl | 51 ++++++++++---- ..._minimum_vertex_splitters_solver_tests.erl | 66 +++++++++++++++++-- 2 files changed, 100 insertions(+), 17 deletions(-) diff --git a/src/core/all_local_minimum_vertex_splitters_solver.erl b/src/core/all_local_minimum_vertex_splitters_solver.erl index b84a0d7..2c8cea2 100644 --- a/src/core/all_local_minimum_vertex_splitters_solver.erl +++ b/src/core/all_local_minimum_vertex_splitters_solver.erl @@ -9,6 +9,9 @@ -type take_all_local_minimum_vertex_splitters() :: fun((dependency_digraph:t()) -> [[dependency_digraph:vertex()]]). +%% =================================================================== +%% Public API +%% =================================================================== % NOTE: % The argument graph is assumed to be a connected DAG. % To reduce computation time, do not check whether `ConnectedDAG` is a connected DAG. @@ -39,17 +42,21 @@ solve_in_exp_time_with_correctness(ConnectedDAG) -> digraph:vertices(ConnectedDAG)), dependency_digraph:is_vertex_splitter(ConnectedDAG, Vertices)], Candidates2 = - lists:usort( - maps:values( + begin + MinimumVertexSplittersForEveryExitVertex = lists:foldl(fun(VertexSplitter, Acc) -> lists:foldl(fun(ExitVertex, Acc2) -> maps:update_with(ExitVertex, - fun(VertexSplitter2) -> - case length(VertexSplitter) - < length(VertexSplitter2) + fun(VertexSplitters) -> + case + compare(length(VertexSplitter), + length(hd(VertexSplitters))) of - true -> VertexSplitter; - false -> VertexSplitter2 + less -> [VertexSplitter]; + greater -> VertexSplitters; + equal -> + [VertexSplitter + | VertexSplitters] end end, Acc2) @@ -57,13 +64,16 @@ solve_in_exp_time_with_correctness(ConnectedDAG) -> Acc, [V || V <- VertexSplitter, - digraph:out_degree(ConnectedDAG, V) =:= 0]) + is_exit_vertex(ConnectedDAG, V)]) end, - maps:from_keys([V - || V <- digraph:vertices(ConnectedDAG), - digraph:out_degree(ConnectedDAG, V) =:= 0], - digraph:vertices(ConnectedDAG)), - Candidates))), + maps:from_keys(exit_vertices(ConnectedDAG), + [digraph:vertices(ConnectedDAG)]), + Candidates), + lists:usort( + lists:map(fun lists:sort/1, + my_lists:flatten( + maps:values(MinimumVertexSplittersForEveryExitVertex)))) + end, [S1 || S1 <- Candidates2, not @@ -72,3 +82,18 @@ solve_in_exp_time_with_correctness(ConnectedDAG) -> sets:from_list(S2), sets:from_list(S1)) end, lists:delete(S1, Candidates2))]. + +%% =================================================================== +%% Private API +%% =================================================================== +-spec compare(term(), term()) -> less | greater | equal. +compare(X, Y) when X < Y -> less; +compare(X, Y) when X > Y -> greater; +compare(_, _) -> equal. + +-spec exit_vertices(digraph:graph()) -> [digraph:vertex()]. +exit_vertices(Digraph) -> + [V || V <- digraph:vertices(Digraph), is_exit_vertex(Digraph, V)]. + +-spec is_exit_vertex(digraph:graph(), digraph:vertex()) -> boolean(). +is_exit_vertex(Digraph, Vertex) -> digraph:out_degree(Digraph, Vertex) =:= 0. diff --git a/test/all_local_minimum_vertex_splitters_solver_tests.erl b/test/all_local_minimum_vertex_splitters_solver_tests.erl index 51f7e58..2214cd6 100644 --- a/test/all_local_minimum_vertex_splitters_solver_tests.erl +++ b/test/all_local_minimum_vertex_splitters_solver_tests.erl @@ -33,7 +33,7 @@ solve_in_polynomial_time_without_correctness_test_() -> solve_in_exp_time_with_correctness_test_() -> {inparallel, - [{"Critical test cases", + [{"Satisfy all", fun() -> G1 = my_digraph:create( lists:seq(1, 6), [{1, 3}, {1, 4}, {2, 4}, {2, 5}, {3, 6}, {4, 6}, {5, 6}]), @@ -51,10 +51,68 @@ solve_in_exp_time_with_correctness_test_() -> lists:map(fun lists:sort/1, all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(G2)))), G3 = my_digraph:create( - lists:seq(1, 3), [{1, 2}, {2, 3}]), + lists:seq(1, 12), + [{1, 4}, + {1, 6}, + {2, 1}, + {2, 6}, + {3, 1}, + {5, 1}, + {6, 7}, + {8, 6}, + {8, 10}, + {9, 8}, + {11, 10}, + {12, 9}]), ?assertEqual(lists:sort( - lists:map(fun lists:sort/1, [[1, 2, 3]])), + lists:map(fun lists:sort/1, [[6, 7], [10]])), + lists:sort( + lists:map(fun lists:sort/1, + all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(G3)))), + G4 = my_digraph:create( + lists:seq(1, 18), + [{1, 2}, + {2, 3}, + {2, 4}, + {4, 5}, + {5, 6}, + {7, 3}, + {8, 9}, + {9, 6}, + {10, 1}, + {10, 11}, + {10, 12}, + {13, 14}, + {14, 12}, + {14, 15}, + {15, 11}, + {15, 16}, + {15, 17}, + {18, 17}]), + ?assertEqual(lists:sort( + lists:map(fun lists:sort/1, [[3], [6], [11, 12], [17]])), + lists:sort( + lists:map(fun lists:sort/1, + all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(G4)))), + G5 = my_digraph:create( + lists:seq(1, 14), + [{1, 2}, + {1, 5}, + {1, 7}, + {2, 3}, + {4, 2}, + {6, 5}, + {7, 8}, + {9, 10}, + {10, 11}, + {10, 12}, + {12, 13}, + {13, 8}, + {13, 14}, + {14, 8}]), + ?assertEqual(lists:sort( + lists:map(fun lists:sort/1, [[2, 3], [5], [8]])), lists:sort( lists:map(fun lists:sort/1, - all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(G3)))) + all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(G5)))) end}]}. From 933fbe76f989ee96bf8021334032788f450b9ea4 Mon Sep 17 00:00:00 2001 From: ajfAfg <56056962+ajfAfg@users.noreply.github.com> Date: Wed, 3 Jan 2024 17:30:59 +0900 Subject: [PATCH 3/3] Test `solve_in_exp_time_with_correctness` for the special case --- ...all_local_minimum_vertex_splitters_solver.erl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/test/prop_all_local_minimum_vertex_splitters_solver.erl b/test/prop_all_local_minimum_vertex_splitters_solver.erl index dff0502..1d624e2 100644 --- a/test/prop_all_local_minimum_vertex_splitters_solver.erl +++ b/test/prop_all_local_minimum_vertex_splitters_solver.erl @@ -86,3 +86,19 @@ prop_solve_in_exp_time_with_correctness3() -> end, all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(ConnectedDAG)) end). + +prop_solve_in_exp_time_with_correctness4(doc) -> + "The return value equals the vertices of the argument graph if only one entrance vertex". + +prop_solve_in_exp_time_with_correctness4() -> + ?FORALL(ConnectedDAG, + dependency_connected_dag(), + begin + ?IMPLIES(length([V + || V <- digraph:vertices(ConnectedDAG), + digraph:in_degree(ConnectedDAG, V) =:= 0]) + =:= 1, + lists:sort( + digraph:vertices(ConnectedDAG)) + =:= lists:sort(hd(all_local_minimum_vertex_splitters_solver:solve_in_exp_time_with_correctness(ConnectedDAG)))) + end).