my ($x, $y) = find_best_partition(map int(rand(1000)), 1..100); #### sub find_best_partition { # We're going to try to find partitions that add up to each possible # number that can be added up to. my $old; my $new = {0 => [[], []]}; for my $n (sort {$a <=> $b} @_) { $old = $new; $new = {}; while (my ($key, $value) = each %$old) { my ($p1, $p2) = @$value; $new->{$key + $n} ||= [[$n, $p1], $p2]; $new->{$key - $n} ||= [$p1, [$n, $p2]]; } } my $best = each %$new; while (my $difference = each %$new) { if (abs($difference) < abs($best)) { $best = $difference; } } # We need to flatten our nested arrays. my ($p1, $p2) = @{ $new->{$best} }; my @part_1; while (@$p1) { push @part_1, $p1->[0]; $p1 = $p1->[1]; } my @part_2; while (@$p2) { push @part_2, $p2->[0]; $p2 = $p2->[1]; } return (\@part_1, \@part_2); } #### sub find_best_partition { my @numbers = sort {abs($b) <=> abs($a) or $a <=> $b} @_; # First we're going to find a "pretty good" partition. # If we can, we'll look for a partition that finishes off # like this one does. That can short-cut the full # algorithm. my @in_partition; my $current_remaining = 0; for my $n (@numbers) { if ($current_remaining < 0) { if ($n > 0) { push @in_partition, 1; $current_remaining += $n; } else { push @in_partition, 0; $current_remaining -= $n; } } else { if ($n > 0) { push @in_partition, 0; $current_remaining -= $n; } else { push @in_partition, 1; $current_remaining += $n; } } } my $known_solution = $current_remaining; # Cheat, we're going to find out the extremes. my @max_sum_of_previous = 0; my $sum = 0; for my $n (@numbers) { $sum += abs($n); push @max_sum_of_previous, $sum; } # We're going to try to find partitions that add up to # each possible number that can be added up to. my $old; my $new = {0 => [[], []]}; my $i = -1; my $answer; N: for my $n (@numbers) { $old = $new; $new = {}; $i++; while (my ($key, $value) = each %$old) { if ($key == -$current_remaining) { # We've found our match! $answer = $value; last N; } if ( abs($key) > $sum - $max_sum_of_previous[$i] + abs($known_solution) ) { # We're too far away from 0 to possibly beat the # "pretty good" partition. So skip. next; } my ($p1, $p2) = @$value; $new->{$key + $n} ||= [[$n, $p1], $p2]; $new->{$key - $n} ||= [$p1, [$n, $p2]]; } # Adjust $current_remaining for the fact we're skipping # the $i'th element. if ($in_partition[$i]) { $current_remaining -= $n; } else { $current_remaining += $n; } } if (not $answer) { $i++; # We need to not append the tail! my $best = each %$new; while (my $difference = each %$new) { if (abs($difference) < abs($best)) { $best = $difference; } } $answer = $new->{$best}; } # We need to flatten our nested arrays and add the tail. my ($p1, $p2) = @$answer; my @part_1; while (@$p1) { push @part_1, $p1->[0]; $p1 = $p1->[1]; } push @part_1 , map { $in_partition[$_] ? $numbers[$_] : () } $i..$#numbers; my @part_2; while (@$p2) { push @part_2, $p2->[0]; $p2 = $p2->[1]; } push @part_2 , map { $in_partition[$_] ? () : $numbers[$_] } $i..$#numbers; return (\@part_1, \@part_2); }