aboutsummaryrefslogtreecommitdiff
path: root/challenge-201/bob-lied/perl/ch-2.pl
blob: d5db0c8ce832ba069bd452dd195cb181603bc9b0 (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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
#!/usr/bin/env perl
# vim:set ts=4 sw=4 sts=4 et ai wm=0 nu:
#=============================================================================
# ch-2.pl Perl Weekly Challenge Week 201 Task 2 Penny Piles
#=============================================================================
# Copyright (c) 2023, Bob Lied
#=============================================================================
# You are given an integer, $n > 0.
# Write a script to determine the number of ways of putting $n pennies
# in a row of piles of ascending heights from left to right.
# Example Input: $n = 5 Output: 7
#   Since $n=5, there are 7 ways of stacking 5 pennies in ascending piles:
#   1 1 1 1 1
#   1 1 1 2
#   1 2 2
#   1 1 3
#   2 3
#   1 4
#   5
#=============================================================================
# This amounts to finding the partitions of a number
# https://en.wikipedia.org/wiki/Partition_(number_theory)
# "No closed-form expression for the partition function is known, but it has
# both asymptotic expansions that accurately approximate it and recurrence
# relations by which it can be calculated exactly."
#
# We will do a recursive expansion and count the results. For each pair of
# possible sums, recurse if the second term is large enough to have sums
# where both terms are bigger than the smaller summand.
# n=7                   n=8
#   1 6                   1 7
#   | 1 5                 | 1 6
#   | | 1 4               | | 1 5
#   | | | 1 3             | | | 1 4
#   | | | | 1 2           | | | | 1 3
#   | | | |   1 1         | | | |   1 2
#   | | | 2 2             | | | |     1 1
#   | | 2 3               | | | | 2 2
#   | 2 4                 | | | 2 3
#   | | 2 2               | | 2 4
#   | 3 3                 | | | 2 2
#   2 5                   | | 3 3
#   | 2 3                 | 2 5
#   3 4                   | | 2 3
#                         | 3 4
#   Total = 15            2 6       Total = 22
#                           2 4
#                             2 2
#                           3 3
#                         3 5
#                         4 4
#=============================================================================

use v5.36;

use Getopt::Long;
my $Verbose = 0;
my $DoTest  = 0;

GetOptions("test" => \$DoTest, "verbose" => \$Verbose);
exit(!runTest()) if $DoTest;

say pennyPiles($_) for @ARGV;

sub pennyPiles($n)
{
    # We're accumulating all the possible orderings. We don't
    # really have to do that to get the answer, but it helps debugging.
    my @result = ( [ $n ] );
    say "PUSH [ $n ]" if $Verbose;
    _pile(1, $n-1, [], \@result, "");

    say showResult(\@result) if $Verbose;
    return scalar(@result);
}

sub _pile($p, $q, $soFar, $result, $indent)
{
    say "${indent}[$soFar->@*] _pile($p, $q)," if $Verbose;

    # Take pairs of summands, but only in one order
    while (  $p <= $q )
    {
        say "${indent}PUSH [ $soFar->@* $p $q ]" if $Verbose;
        push @$result, [ $soFar->@*, $p, $q ];

        # If the second term can be split into additions where both
        # terms are greater than p, then recurse to that.
        # For example, (2 6) can split the 6 into 2+4 or 3+3, but not
        # 1+5 because the 1 would violate the ordering rule.
        # For (3 4) we can't split the 4 in a way where both terms
        # are at least 3.
        if ( $q >= 2*$p )
        {
            _pile($p, $q-$p, [ $soFar->@*, $p ], $result, "  $indent");
        }
        $p++; $q--;
    }
}

sub showResult($result)
{
    for my $array ($result->@*) # ( sort { $#{$a} <=> $#{$b} } $result->@* )
    {
        say "[ $array->@* ]";
    }
}

sub runTest
{
    use Test2::V0;

    #is( pennyPiles(1),  1, "Test 1");
    #is( pennyPiles(2),  2, "Test 2");
    #is( pennyPiles(3),  3, "Test 3");
    #is( pennyPiles(4),  5, "Test 4");
    #is( pennyPiles(5),  7, "Example 1");
    is( pennyPiles(6), 11, "Test 6");
    is( pennyPiles(7), 15, "Test 7");
    is( pennyPiles(8), 22, "Test 8");

    done_testing;
}