aboutsummaryrefslogtreecommitdiff
path: root/challenge-124/colin-crain/perl/ch-2.pl
blob: 0b7b6a3e626f35876c9954f48fe8b572c25a7ad8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
#!/Users/colincrain/perl5/perlbrew/perls/perl-5.32.0/bin/perl
#
#       .pl
#
#       Tug of War
#         Submitted by: Mohammad S Anwar
#         You are given a set of $n integers (n1, n2, n3, ….).
# 
#         Write a script to divide the set in two subsets of n/2 sizes each so
#         that the difference of the sum of two subsets is the least. If $n is
#         even then each subset must be of size $n/2 each. In case $n is odd
#         then one subset must be ($n-1)/2 and other must be ($n+1)/2.
# 
#         Example
# 
#             Input:        Set = (10, 20, 30, 40, 50, 60, 70, 80, 90, 100)
#             Output:  Subset 1 = (30, 40, 60, 70, 80)
#                      Subset 2 = (10, 20, 50, 90, 100)
#     
#             Input:        Set = (10, -15, 20, 30, -25, 0, 5, 40, -5)
#                      Subset 1 = (30, 0, 5, -5)                        = 30
#                      Subset 2 = (10, -15, 20, -25, 40)                = 30
# 
#       method:
#         Did we do something like this already? In any case an algorithm
#         springs immediately to mind: sort the input, and distribute the two
#         largest values betwen the other lists. 

#         Yea, but negative values complicate that algorithm, especially when it
#         comes to making sure the divided parts are of equal-ish size. After
#         exploring that angle, I decided it was too complicated. So rather than
#         that, we're going to exhaustively compute the combinations n choose k
#         for half the elements, rounding down. Subtracting the sum of the
#         combination from the total sum of the list gives the fitness metric to
#         be minimized.
# 
#         Actually the difference of the part versus half the total will be the
#         same for the other half, so we can construct the metric by doubling
#         the partial sum and subtracting that, which is easier. We keep a
#         running minimum, keeping the difference between the segments and the
#         top-rated combination.
# 
#         At the end we need to create the complement segment, which we do by
#         finding the index of and then splicing out the elements in the
#         minimizing combination from the original inoput list. 
# 
#       © 2021 colin crain
## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ## ##



use warnings;
use strict;
use utf8;
use feature ":5.26";
use feature qw(signatures);
no warnings 'experimental::signatures';

use List::Util qw( sum first );
use Algorithm::Combinatorics qw( combinations );

my @input = sort { $a <=> $b } @ARGV > 0 
    ? @ARGV
    : (10, -15, 20, 30, -25, 0, 5, 40, -5);
    
die "must have more than 1 element in input array" if @input < 2;

my $sum = my $min = sum( @input );
my @part1;

my $half = int( @input/2 );
my $iter = combinations(\@input, $half);
while (my $c = $iter->next) {
    my $partial = sum $c->@*;
    if (abs($sum - 2 * $partial) < $min) {
        $min = abs($sum - 2 * $partial);
        @part1 = $c->@*;
    }
}

for my $target ( @part1 ) {
    splice @input, (first { $input[$_] == $target } (0..$#input)), 1;
}

my $output =
  (join ' + ', sort {$b<=>$a} @part1) . ' = ' . sum( @part1 ) . "\n" 
. (join ' + ', sort {$b<=>$a} @input) . ' = ' . sum( @input );

$output =~ s/\+ \-/- /g;

say $output;