(*
Steve The Gambler: Caught by the IRS
A subset-sum problem with a constraint on the size of the subset to find
cameron {at} theaboutbox {dot} com
If you want to use this code, you can, without warranty.
*)
(* Steve's daily winnings *)
let daily_winnings = [-50; -21; 13; 171; 14; -42; -58; 109; 4; 7; -23;
-44; -98; -121; 101; 33; 87; -121; -40; -65; 43;
54; -45; -12; -12; 38; 25; 3; 7; 8];;
(* Splits a list into positive and negative sublists. Sorts the lists so the
numbers closest to zero are first. This way, if there is no ideal list, we
create the smallest non-ideal subset we can. *)
let rec split_list l pos neg = match l with
[] -> ((List.sort compare pos),(List.rev (List.sort compare neg)))
| h::t when h < 0 -> split_list t pos (h::neg)
| h::t -> split_list t (h::pos) neg;;
(* Sums a list *)
let sum = List.fold_left (+) 0;;
(* Prints an int list. Thanks, Andrew! *)
let rec print_int_list = function
| [] -> ()
| x::xs -> print_int x; print_string " "; print_int_list xs;;
(*
Subset sum solver that looks for a sublist of the given size that sums to zero.
Splits the list into positive and negative sublists to limit the number of
decisions at any point in searching the problem space. When the running sum is
positive, choose negative numbers and when the running sum is negative, choose
positive numbers.
We employ some tricks to limit the effort spent searching because we know the size
of the subset we need to find. If we are deep enough in the search space that there
is only one outcome, we return it immediately. If we run out of positive numbers
and the running sum is negative, we fill out the list with the smallest positive
numbers we can and vice versa.
*)
let subset_sum list size =
let (pos,neg) = split_list list [] [] in
(* Returns true if this list is the right size and sums to zero *)
let suitable l = (List.length l) = size && (sum l) = 0 in
(* Returns the best list: the one whose sum is closest to zero *)
let best_list l1 l2 = match (l1, l2) with
([], l2) -> l2
| (l1, []) -> l1
| (l1, l2) -> let s1 = abs (sum l1) and s2 = abs (sum l2) in
if s1 < s2 then l1 else l2 in
let rec subset_aux l s p n = match (l,s,p,n) with
(list, sum, _, _) when (List.length list) = size -> list
(* Reached a dead end in the search space - only one possible outcome *)
| (list, _, p, n) when (List.length list) + (List.length p) + (List.length n) = size ->
list @ p @ n
(* Have a positive sum and only negative numbers: pile on the smallest neg #s possible *)
| (list, sum, [], n::t) when sum < 0 -> subset_aux (n::list) (sum+n) [] t
(* Have a negative sum and only positive numbers: pile on the smallest pos #s possible *)
| (list, sum, p::t, []) when sum > 0 -> subset_aux (p::list) (sum+p) t []
(* Negative sum: pick a positive number and recurse *)
| (list, sum, p::t, n) when sum <= 0 ->
let with_list = subset_aux (p::list) (sum+p) t n in
if (suitable with_list) then with_list
else let without_list = subset_aux list sum t n in
best_list with_list without_list
(* Positive sum: pick a negative number and recurse *)
| (list, sum, p, n::t) ->
let with_list = subset_aux (n::list) (sum+n) p t in
if (suitable with_list) then with_list
else let without_list = subset_aux list sum p t in
best_list with_list without_list
in
subset_aux [] 0 pos neg;;
let read_days =
if (Array.length Sys.argv) = 1 then 9
else int_of_string Sys.argv.(1);;
let _ =
let best_list = subset_sum daily_winnings read_days in
print_string "Best List: ";
print_int_list best_list;
print_string "\n";;
- View this file Here (Right Click and save As)