aboutsummaryrefslogtreecommitdiff
path: root/challenge-098/jo-37/perl/ch-2.pl
blob: 2ae13d84a1b2844b6cdc80fac5dd6a3ec41a569a (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
#!/usr/bin/perl -s

use v5.16;
use Test2::V0;
use warnings FATAL => 'all';
use List::MoreUtils 0.423 'lower_bound';
use experimental 'signatures';

our ($tests, $examples, $verbose, $value);

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

die <<EOS unless defined $value;
usage: $0 [-examples] [-tests] [-verbose] [-value=num [--] n1 n2...]

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

-verbose
    show resulting index and array

-value=num
    number to insert or search for

n1 n2...
    sorted list of numbers

EOS

sub find_or_insert :prototype($\@);


### Input and Output

say scalar find_or_insert $value, @ARGV;


### Implementation

# Search for a value in the given sorted array insert it into the proper
# position if missing.
#
# Note 1: Has to be called with a true array as second argument.
# Note 2: Returns the index position when called in scalar context or a
#         reference to the resulting array together with the index
#         position in list context.
sub find_or_insert ($val, $arr) :prototype($\@) {

    # A binary search is the tool of choice to operate on sorted data.
    # "lower_bound" provides the wanted index accordingly.  Select zero
    # for an empty array.
    my $idx = @$arr ? lower_bound {$_ <=> $val} @$arr : 0;

    # Insert the value at the identified position if missing.  Use a
    # virtually appended 'inf' to force a "push" operation.
    splice @$arr, $idx, 0, $val if ($arr->[$idx] // 'inf') > $val;
    say "$idx: (@$arr)" if $verbose;

    # Return a reference to the resulting array and the index.
    ($arr, $idx);
}


### Examples and tests

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

        my @a;

        @a = (1, 2, 3, 4);
        is find_or_insert(3, @a), 2, 'example 1';
        is [@a], [1, 2, 3, 4], 'resulting array for example 1';

        @a = (1, 3, 5, 7);
        is find_or_insert(6, @a), 3, 'example 2';
        is [@a], [1, 3, 5, 6, 7], 'resulting array for example 2';

        @a = (12, 14, 16, 18);
        is find_or_insert(10, @a), 0, 'example 3';
        is [@a], [10, 12, 14, 16, 18], 'resulting array for example 3';

        @a = (11, 13, 15, 17);
        is find_or_insert(19, @a), 4, 'example 4';
        is [@a], [11, 13, 15, 17, 19], 'resulting array for example 4';
    }

    SKIP: {
        skip 'tests' unless $tests;
 
        # Provide list context to retrieve the resulting array.

        is [find_or_insert 0, @{[]}], [[0], 0],
            'insert zero into empty list';

        is [find_or_insert 0, @{[0]}], [[0], 0],
            'find zero';

        is [find_or_insert -1, @{[0]}], [[-1, 0], 0],
            'insert negative value';
    }

    done_testing;
    exit;
}