aboutsummaryrefslogtreecommitdiff
path: root/challenge-200/jo-37/perl/ch-1.pl
blob: 422065663be07e7fa2ff32cbdcc66b04684e24a1 (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/perl -s

use v5.16;
use Test2::V0 '!float';
use PDL;
use PDL::NiceSlice;
use experimental 'postderef';

our ($tests, $examples);

run_tests() if $tests || $examples;	# does not return

die <<EOS unless @ARGV;
usage: $0 [-examples] [-tests] [--] [N...]

-examples
    run the examples from the challenge
 
-tests
    run some tests

N...
    list of numbers

EOS


### Input and Output

main: {
    local $" = ',';
    local $, = ', ';
    say map "(@$_)", arith_slices(@ARGV);
}


### Implementation

# Using PDL to solve the task.  Overkill, once again.

# Some details remain unspecified:
# - the presentation order of the found arithmetic slices
# - the treatment of different subslices having the same values
# This implementation does:
# - order maximal arithmetic slices by starting index
# - find subslices by increasing length and by starting index within the
#   same length
# - regard subslices having a different starting index or length as
#   different, regardless of the values therein.

sub arith_slices {
    return if @_ < 3;
    my $l = long @_;

    # Calculate differences between consecutive pairs and run-length
    # encode these.  Only the run lengths are required, the differences
    # get dropped.
    my ($rl) = rle $l(1:-1) - $l(0:-2);
    
    # The starting indices of each run can be found by taking the
    # cumulative sums over the run lengths starting with zero.
    # There are several way to calculate the zero-based cumulative sums.
    # One of them is to take the difference between the total sum and
    # the reversed cumulative sums over the reversed list, as can be
    # seen from this example:
    # (a, b, c) -> ((a + b + c) - (c + b + a), (a + b + c) - (c + b),
    # (a + b + c) - c) = (0, a, a + b)
    # The total sum over the run length equals the length of the list of
    # differences which is one less than the original list's length.
    my $idx = $l->dim(0) - 1 - $rl(-1:0)->cumusumover->(-1:0);

    # The differences' run lengths are one below the subarray lengths,
    # adjust them.
    $rl += 1;

    # Select subarrays having a minimum length of three, given by their
    # starting index and their length.
    my @res;
    for my $il (cat(where $idx, $rl, $rl > 2)->xchg(0, 1)->dog) {
        # Pick a maximal arithmetic array slice given by index and
        # lenght.
        my $maas = $l->range($il->list);

        # Split each maximal arithmetic array slice into all requested
        # array slices and collect these.
        push @res, map $maas->lags(0, 1, $_)
            ->xchg(0, 1)->(-1:0)->unpdl->@*, 3 .. $maas->dim(0);
    }

    @res;
}

### Examples and tests

sub run_tests {
    SKIP: {
        skip "examples" unless $examples;

        is [arith_slices(1, 2, 3, 4)],
            [[1, 2, 3], [2, 3, 4], [1, 2, 3, 4]], 'example 1';

        is [arith_slices(2)], [], 'example 2';

    }

    SKIP: {
        skip "tests" unless $tests;

        is [arith_slices(1, 2, 3, 4, 3, 5, 7)],
            [[1, 2, 3], [2, 3, 4], [1, 2, 3, 4], [3, 5, 7]],
            'different step sizes';

        is [arith_slices(-2, 0, 2)], [[-2, 0, 2]], 'negative value';

        is [arith_slices(1, 2, 3, 5, 7)], [[1, 2, 3], [3, 5, 7]],
            'adjacent slices';

        is [arith_slices(0, 0, 0, 0)], [[0, 0, 0], [0, 0, 0], [0, 0, 0, 0]],
            'slices having equal values';
	}

    done_testing;
    exit;
}