File Coverage

blib/lib/Array/Ordered.pm
Criterion Covered Total %
statement 180 196 91.8
branch 47 68 69.1
condition 10 21 47.6
subroutine 33 36 91.6
pod 17 17 100.0
total 287 338 84.9


line stmt bran cond sub pod time code
1             package Array::Ordered;
2              
3 2     2   39419 use 5.006;
  2         8  
  2         88  
4 2     2   12 use strict;
  2         3  
  2         70  
5 2     2   10 use warnings FATAL => 'all';
  2         8  
  2         87  
6 2     2   1589 use integer;
  2         19  
  2         11  
7 2     2   1798 use subs qw( last unshift push shift pop sort );
  2         41  
  2         10  
8 2     2   152 use Scalar::Util qw( blessed );
  2         3  
  2         181  
9 2     2   12 use Carp;
  2         4  
  2         5903  
10              
11             =head1 NAME
12              
13             Array::Ordered - Methods for handling ordered arrays
14              
15             =cut
16              
17             require Exporter;
18              
19             our @ISA = qw( Exporter );
20             our @EXPORT = qw( order );
21             our @EXPORT_OK = qw( order );
22              
23             =head1 VERSION
24              
25             Version 0.03
26              
27             =cut
28              
29             our $VERSION = '0.03';
30              
31             =head1 SYNOPSIS
32              
33             use Array::Ordered;
34            
35             # Export
36             $array = order [], \&my_comparison;
37             $array = order \@array, \&my_comparison;
38             $array = order $array, \&other_comparison;
39            
40             # Utility
41             $size = $array->size;
42             @items = $array->clear; # $array->size == 0
43            
44             # Strictly Ordered:
45             $elem = $array->find $match;
46             $array->insert $item;
47             $elem = $array->find_or_insert $match;
48             $item = $array->remove $match;
49             $pos = $array->position $match;
50             unless( $array->is_reduced ) {
51             $array->reduce;
52             }
53            
54             # Unstrictly Ordered:
55             $elem = $array->first $match;
56             $elem = $array->last $match;
57             $array->unshift @items;
58             $array->push @items;
59             $item = $array->shift $match;
60             $item = $array->pop $match;
61             $pos = $array->first_position $match;
62             $pos = $array->last_position $match;
63             $count = $array->occurrences $match;
64             unless( $array->is_sorted ) {
65             $array->sort;
66             }
67            
68             # Multi-element:
69             @elems = $array->find_all $match;
70             @elems = $array->heads;
71             @elems = $array->tails;
72             @items = $array->remove_all $match;
73             @items = $array->shift_heads;
74             @items = $array->pop_tails;
75              
76             =head1 DESCRIPTION
77              
78             The purpose of the Array::Ordered module is to provide the means to access and modify arrays while keeping them sorted.
79              
80             At the heart of this module are two symmetrical binary search algorithms:
81              
82             =over
83              
84             =item 1
85              
86             The first returns the index of the first element equal to or greater than a matching argument. (possibly the array's size)
87              
88             =item 2
89              
90             The second returns the index of the last element equal to or less than a matching argument. (possibly -1)
91              
92             =back
93              
94             Elements are inserted and deleted from the ordered array using 'splice'.
95              
96             =head2 TERMINOLOGY
97              
98             =head3 Comparison Subroutine
99              
100             A I takes two arguments - each a scalar or a reference - and returns a numeric scalar:
101              
102             =over
103              
104             =item
105              
106             Negative if the first argument should preceed the second (less than)
107              
108             =item
109              
110             Zero if they are equivalent (equal to)
111              
112             =item
113              
114             Positive if the first argument should follow the second (greater than)
115              
116             =back
117              
118             =head3 Equivalency Sequence
119              
120             =begin html
121              
122            

Consider an array A = <X0, X1, X2, Y0, Z0, Z1> sorted by the rule C such that:

123              
124            
125            
  • C (X*, X*) = 0,
  • 126             C (X*, Y*) < 0,
    127             C (X*, Z*) < 0,
    128            
  • C (Y*, X*) > 0,
  • 129             C (Y*, Y*) = 0,
    130             C (Y*, Z*) < 0,
    131            
  • C (Z*, X*) > 0,
  • 132             C (Z*, Y*) > 0,
    133             C (Z*, Z*) = 0
    134            
    135              
    136            

    The array A has three equivalency sequences: AX = <X0, X1, X2>, AY = <Y0>, and AZ = <Z0, Z1>.

    137              
    138             =end html
    139              
    140             The length of every equivalency sequence in a strictly ordered array is 1. Only an unstrictly ordered array can have longer equivalency sequences.
    141              
    142             =head1 METHODS
    143              
    144             I have used the following convention for naming variables:
    145              
    146             =over
    147              
    148             =item *
    149              
    150             A variable is named C<$item> or C<@items> if it refers to data introduced into or removed from the ordered array.
    151              
    152             =item *
    153              
    154             A variable is named C<$elem> or C<@elems> if it refers to data accessed and remaining in the ordered array.
    155              
    156             =item *
    157              
    158             An argument is named C<$match> when it is used to fish out one or more equivalent elements from the array.
    159              
    160             =back
    161              
    162             =head2 Export
    163              
    164             =head3 order
    165              
    166             This method takes two arguments:
    167              
    168             =over
    169              
    170             =item 1
    171              
    172             An array reference, and
    173              
    174             =item 2
    175              
    176             A reference to a comparison subroutine.
    177              
    178             =back
    179              
    180             The array reference is returned after being tied to the code reference for ordering, the array's contents are sorted, and the reference is blessed.
    181              
    182             The method C is exported implicitly. The decision for this is due to the fact that none of the module's other methods are of any use without it. Consider it this module's "C" method.
    183              
    184             sub lencmp { length $_[0] <=> length $_[1] }
    185            
    186             $array = order [], \&lencmp; # Empty array orded by 'lencmp'
    187            
    188             order $array, sub { $_[0] cmp $_[1] }; # Now ordered by 'cmp'
    189            
    190             $array = order []; # Okay: Default comparison is sub { 0 }
    191            
    192             my @items = { '3', '001', '02' };
    193              
    194             $array = order [@items], \&lencmp; # Copy of @items ordered by '&lencmp':
    195             # @items is unchanged
    196             $array = order \@items, \&lencmp; # $array == \@items:
    197             # @items is sorted
    198              
    199             =cut
    200              
    201             my %CMPSUBS;
    202              
    203             sub order {
    204             # @_ == ($self, $cmpsub);
    205 165 50   165 1 29669 my @valid = (
        50          
        50          
    206             defined $_[0] ?
    207             blessed $_[0] ? $_[0]->isa('Array::Ordered') : ref $_[0] eq 'ARRAY' :
    208             '',
    209             defined $_[1] ? ref $_[1] eq 'CODE' : 1
    210             );
    211              
    212 165 50 33     576 unless ($valid[0] and $valid[1]) {
    213 0         0 my @msg = ('Array::Ordered::order');
    214 0 0       0 (defined $_[0]) or
    215             push @msg, 'missing argument';
    216 0         0 foreach my $i (0 .. 1) {
    217 0 0 0     0 (!$valid[$i] and defined $_[$i]) and
          0        
    218             push @msg, 'invalid argument '.(ref $_[0] || $_[0]);
    219             }
    220 0         0 croak join( ': ', @msg );
    221             }
    222            
    223 165         242 my ($self,
    224             $cmpsub) = @_;
    225 165 50       321 (defined $cmpsub) or
    226             $cmpsub = \&_default_cmpsub;
    227              
    228 165 50       453 (blessed $self) or bless $self;
    229              
    230 165 100 100     604 unless (exists $CMPSUBS{$self} and
    231             $CMPSUBS{$self} == $cmpsub) {
    232 154         383 $CMPSUBS{$self} = $cmpsub;
    233 154         318 $self->sort;
    234             }
    235              
    236 165         953 return $self;
    237             }
    238              
    239             =head2 Utility
    240              
    241             =head3 size
    242              
    243             Returns number of elements in referenced array.
    244              
    245             $size = $array->size;
    246             # Same as:
    247             $size = scalar @{$array};
    248              
    249             =cut
    250              
    251             sub size {
    252 1     1 1 6 return scalar( @{$_[0]} );
      1         7  
    253             }
    254              
    255             =head3 clear
    256              
    257             Removes and returns all elements from the ordered array.
    258              
    259             @array_contained = $array->clear;
    260             # Same as:
    261             @array_contained = splice( @{$array}, 0, $array->size );
    262              
    263             =cut
    264              
    265             sub clear {
    266 7     7 1 29 return splice( @{$_[0]}, 0, scalar( @{$_[0]} ) );
      7         15  
      7         44  
    267             }
    268              
    269             =head2 Strictly Ordered
    270              
    271             =head3 find
    272              
    273             Alias for L|/first>.
    274              
    275             =head3 insert
    276              
    277             Alias for L|/push>.
    278              
    279             =head3 find_or_insert
    280              
    281             Returns first equivalent item if found, or inserts and returns a new item.
    282              
    283             If no equivalent item is found, then:
    284              
    285             =begin html
    286              
    287            
    288            
  • If a code reference is passed, its return value is inserted; otherwise,
  • 289            
  • If a default value is passed, its value is inserted; otherwise,
  • 290            
  • The method inserts the value of $match.
  • 291            
    292              
    293             =end html
    294              
    295             $object = $array->find_or_insert( $match, \&constructor );
    296             $elem = $array->find_or_insert( $match, $default );
    297             $elem = $array->find_or_insert( $match );
    298            
    299             # Examples:
    300             $object = $array->find_or_insert( 'Delta', sub { My::NamedObject->new( 'Delta' ) } );
    301             $elem = $array->find_or_insert( 'DELTA', 'Delta' );
    302             $elem = $array->find_or_insert( 'Delta' );
    303              
    304             Use C whenever possible! This is the only insertion method which verifies that the array is strictly ordered.
    305              
    306             =cut
    307              
    308             sub find_or_insert {
    309             # @_ == ($self, $match, $constr:undef);
    310 357     357 1 1189 my ($self,
    311             $match,
    312             $constr) = @_;
    313 357         627 my $found = $self->first( $match );
    314              
    315 357 100       688 unless (defined $found) {
    316 37         78 $found = (defined $constr) ?
    317 111 100       251 (ref $constr eq 'CODE') ? &{$constr} : $constr :
        100          
    318             $match;
    319 111         261 $self->push( $found );
    320             }
    321              
    322 357         727 return $found;
    323             }
    324              
    325             =head3 remove
    326              
    327             Alias for L|/shift>.
    328              
    329             =head3 position
    330              
    331             Alias for L|/first_position>.
    332              
    333             =head3 is_reduced
    334              
    335             Returns C<1> if the array is strictly ordered, otherwise C<''>.
    336              
    337             $strictly = $array->is_reduced;
    338              
    339             =cut
    340              
    341             sub is_reduced {
    342             # @_ == ($self)
    343 21     21 1 95 my ($self) = @_;
    344 21         44 my $cmpsub = $CMPSUBS{$self};
    345 21         23 my $size = scalar @{$self};
      21         38  
    346            
    347 21         64 for (my $i = 1; $i < $size; $i++) {
    348 75 100       270 (&{$cmpsub}( $self->[$i-1], $self->[$i] ) < 0) or
      75         151  
    349             return '';
    350             }
    351            
    352 14         80 return 1;
    353             }
    354              
    355             =head3 reduce
    356              
    357             Reduces the array into a strictly ordered array.
    358              
    359             Only the last element of each equivalency sequence remains unless a C argument is passed, in which case only the first of each remains.
    360              
    361             $array->reduce;
    362             # Same as:
    363             $array->reduce( 0 );
    364            
    365             # Or use:
    366            
    367             my $preserve_first = 1;
    368             $array->reduce( $preserve_first );
    369              
    370             =cut
    371              
    372             sub reduce {
    373             # @_ == ($self, $preserve_first)
    374 14     14 1 48 my ($self,
    375             $preserve_first) = @_;
    376 14         28 my $cmpsub = $CMPSUBS{$self};
    377 14         14 my $size = scalar @{$self};
      14         19  
    378              
    379             # Default behavior is FIFO: delete first unless otherwise specified
    380 14 100       49 my $preserve_last = $preserve_first ? 0 : 1;
    381            
    382 14         14 my $i = 1;
    383 14         31 while ($i < $size) {
    384 224         256 my $cmp = &{$cmpsub}( $self->[$i-1], $self->[$i] );
      224         399  
    385 224 100       693 if ($cmp < 0) {
        50          
    386 60         129 $i++;
    387             }
    388             elsif ($cmp == 0) {
    389 164         166 splice( @{$self}, $i - $preserve_last, 1 );
      164         208  
    390 164         372 $size--;
    391             }
    392             else {
    393 0         0 my $item = splice @{$self}, $i, 1;
      0         0  
    394 0         0 my $index = _search_down( $self, $item, $i - 2 );
    395              
    396 0 0 0     0 if ($index < 0 or
      0         0  
    397             &{$cmpsub}( $self->[$index], $item ) < 0) {
    398 0         0 _insert( $self, $item, $index + 1 );
    399             }
    400             else { # &{$cmpsub}( $item, $self->[$index] ) == 0
    401 0 0       0 $self->[$index] = $item unless ($preserve_first);
    402 0         0 $size--;
    403             }
    404             }
    405             }
    406             }
    407              
    408             =head2 Unstrictly Ordered
    409              
    410             =head3 first
    411              
    412             Returns first equivalent item or C if not found.
    413              
    414             Optionally returns the position of the item or C if not found. (via C)
    415              
    416             $elem = $array->first( $match );
    417             ($elem, $pos) = $array->first( $match );
    418              
    419             =cut
    420              
    421             sub first {
    422             # @_ == ($self, $match)
    423 394     394 1 2028 my ($found,
    424             $equal,
    425             $index) = _find( @_, \&_search_up);
    426            
    427 394 100       830 $equal or
    428             ($found,
    429             $index) = (undef, undef);
    430            
    431 394 100       1003 return wantarray ? ($found, $index) : $found;
    432             }
    433              
    434             =head3 last
    435              
    436             Returns last equivalent item or C if not found.
    437              
    438             Optionally returns the position of the item or C if not found. (via C)
    439              
    440             $elem = $array->last( $match );
    441             ($elem, $pos) = $array->last( $match );
    442              
    443             =cut
    444              
    445             sub last {
    446             # @_ == ($self, $match)
    447 37     37   1351 my ($found,
    448             $equal,
    449             $index) = _find( @_, \&_search_down );
    450            
    451 37 50       88 $equal or
    452             ($found,
    453             $index) = (undef, undef);
    454            
    455 37 50       169 return wantarray ? ($found, $index) : $found;
    456             }
    457              
    458             =head3 unshift
    459              
    460             Adds item(s), prepending them to their equivalent peers.
    461              
    462             $array->unshift( $item );
    463             $array->unshift( @items );
    464              
    465             =cut
    466              
    467             sub unshift {
    468             # @_ == ($self, @items)
    469 7     7   39 my $self = CORE::shift;
    470              
    471 7         16 foreach (@_) {
    472 119         252 _insert( $self, $_, _search_up( $self, $_ ) );
    473             }
    474             }
    475              
    476             =head3 push
    477              
    478             Adds item(s), appending them to their equivalent peers.
    479              
    480             $array->push( $item );
    481             $array->push( @items );
    482              
    483             =cut
    484              
    485             sub push {
    486             # @_ == ($self, @items)
    487 118     118   171 my $self = CORE::shift;
    488              
    489 118         192 foreach (@_) {
    490 230         405 _insert( $self, $_, _search_down( $self, $_ ) + 1 );
    491             }
    492             }
    493              
    494             =head3 shift
    495              
    496             Removes and returns first equivalent item or C if not found.
    497              
    498             $item = $array->shift( $match );
    499              
    500             =cut
    501              
    502             sub shift {
    503             # @_ == ($self, $match)
    504 190     190   1575 return _remove( @_, \&_search_up );
    505             }
    506              
    507             =head3 pop
    508              
    509             Removes and returns last equivalent item or C if not found.
    510              
    511             $item = $array->pop( $match );
    512              
    513             =cut
    514              
    515             sub pop {
    516             # @_ == ($self, $match)
    517 190     190   1484 return _remove( @_, \&_search_down );
    518             }
    519              
    520             =head3 first_position
    521              
    522             Returns position of first equivalent item or C if not found.
    523              
    524             $pos = $array->first_position( $match );
    525             # Same as:
    526             $pos = ($array->first( $match ))[1];
    527              
    528             =cut
    529              
    530             sub first_position {
    531 0     0 1 0 return (first( @_ ))[1];
    532             }
    533              
    534             =head3 last_position
    535              
    536             Returns position of last equivalent item or C if not found.
    537              
    538             $pos = $array->last_position( $match );
    539             # Same as:
    540             $pos = ($array->last( $match ))[1];
    541              
    542             =cut
    543              
    544             sub last_position {
    545 0     0 1 0 return (last( @_ ))[1];
    546             }
    547              
    548             =head3 occurrences
    549              
    550             Returns number of elements equivalent to C<$match>.
    551              
    552             $count = $array->occurrences( $match );
    553              
    554             =cut
    555              
    556             sub occurrences {
    557             # @_ == ($self, $match)
    558 74     74 1 281 my ($found,
    559             $equal,
    560             $from) = _find( @_, \&_search_up );
    561              
    562 74 50       194 return $equal ? _search_down( @_ ) - $from + 1 : 0;
    563             }
    564              
    565             =head3 is_sorted
    566              
    567             Returns C<1> if the array is ordered, otherwise C<''>.
    568              
    569             There is no need to call this method as long as the referenced array is modified only via the methods in this module.
    570              
    571             $ordered = $array->is_sorted;
    572              
    573             =cut
    574              
    575             sub is_sorted {
    576             # @_ == ($self)
    577 13     13 1 105 my ($self) = @_;
    578 13         21 my $cmpsub = $CMPSUBS{$self};
    579 13         16 my $size = scalar @{$self};
      13         17  
    580            
    581 13         31 for (my $i = 1; $i < $size; $i++) {
    582 83 100       336 (&{$cmpsub}( $self->[$i-1], $self->[$i] ) > 0) and
      83         151  
    583             return '';
    584             }
    585            
    586 3         17 return 1;
    587             }
    588              
    589             =head3 sort
    590              
    591             Sorts the referenced array using its associated comparison subroutine.
    592              
    593             There is no need to call this method as long as the referenced array is modified only via the methods in this module.
    594              
    595             $array->sort;
    596              
    597             =cut
    598              
    599             sub sort {
    600             # @_ == ($self)
    601 164     164   247 my ($self) = @_;
    602 164         294 my $cmpsub = $CMPSUBS{$self};
    603 164         177 my $size = scalar @{$self};
      164         258  
    604              
    605 164         454 for (my $i = 1; $i < $size; $i++) {
    606 1816 100       7736 if (&{$cmpsub}( $self->[$i], $self->[$i-1] ) < 0) {
      1816         3666  
    607 252         941 my $item = $self->[$i];
    608 252         507 my $index = _search_down( $self, $item, $i - 2) + 1;
    609 252         570 for (my $j = $i; $j > $index; $j--) {
    610 1109         2582 $self->[$j] = $self->[$j-1];
    611             }
    612 252         730 $self->[$index] = $item;
    613             }
    614             }
    615             }
    616              
    617             =head2 Multi-element
    618              
    619             =head3 find_all
    620              
    621             Returns array of all items equivalent to C<$match>.
    622              
    623             @elems = $array->find_all( $match );
    624              
    625             =cut
    626              
    627             sub find_all {
    628             # @_ == ($self, $match)
    629 74     74 1 308 my ($found,
    630             $equal,
    631             $from) = _find( @_, \&_search_up );
    632              
    633 74 50       208 return $equal ? @{$_[0]}[$from .. _search_down( @_ )] : ();
      74         350  
    634             }
    635              
    636             =head3 heads
    637              
    638             Returns a strictly ordered array containing the first of each equivalency sequence.
    639              
    640             @elems = $array->heads;
    641              
    642             =cut
    643              
    644             sub heads {
    645             # @_ == ($self)
    646 7     7 1 23 my ($self) = @_;
    647 7         9 my $size = scalar( @{$self} );
      7         10  
    648 7         8 my @heads;
    649            
    650 7         17 for (my $index = 0; $index < $size;
    651             $index = _search_down( $self, $heads[-1] ) + 1) {
    652 37         90 CORE::push @heads, $self->[$index];
    653             }
    654              
    655 7         40 return @heads;
    656             }
    657              
    658             =head3 tails
    659              
    660             Returns a strictly ordered array containing the last of each equivalency sequence.
    661              
    662             @elems = $array->tails;
    663              
    664             =cut
    665              
    666             sub tails {
    667             # @_ == ($self)
    668 7     7 1 28 my ($self) = @_;
    669 7         8 my @tails;
    670            
    671 7         11 for (my $index = scalar( @{$self} ) - 1; $index >= 0;
      7         25  
    672             $index = _search_up( $self, $tails[0] ) - 1) {
    673 37         113 CORE::unshift @tails, $self->[$index];
    674             }
    675              
    676 7         66 return @tails;
    677             }
    678              
    679             =head3 remove_all
    680              
    681             Removes all items equivalent to C<$match> and returns them as an array.
    682              
    683             @items = $array->remove_all( $match );
    684              
    685             =cut
    686              
    687             sub remove_all {
    688             # @_ == ($self, $match)
    689 74     74 1 336 my ($found,
    690             $equal,
    691             $from) = _find( @_, \&_search_up );
    692              
    693 74         191 return $equal ?
    694 74 50       164 splice( @{$_[0]}, $from, _search_down( @_ ) - $from + 1 ) : ();
    695             }
    696              
    697             =head3 shift_heads
    698              
    699             Removes the first of each equivalency sequence and returns them as a strictly ordered array.
    700              
    701             @items = $array->shift_heads;
    702              
    703             =cut
    704              
    705             sub shift_heads {
    706             # @_ == ($self)
    707 7     7 1 30 my ($self) = @_;
    708 7         9 my $size = scalar( @{$self} );
      7         58  
    709 7         10 my @heads;
    710              
    711 7         24 for (my $index = 0; $index < $size; $size--,
    712             $index = _search_down( $self, $heads[-1] ) + 1) {
    713 37         46 CORE::push @heads, splice( @{$self}, $index, 1 );
      37         123  
    714             }
    715              
    716 7         46 return @heads;
    717             }
    718              
    719             =head3 pop_tails
    720              
    721             Removes the last of each equivalency sequence and returns them as a strictly ordered array.
    722              
    723             @items = $array->pop_tails;
    724              
    725             =cut
    726              
    727             sub pop_tails {
    728             # @_ == ($self)
    729 7     7 1 27 my ($self) = @_;
    730 7         8 my @tails;
    731            
    732 7         9 for (my $index = scalar( @{$self} ) - 1; $index >= 0;
      7         25  
    733             $index = _search_up( $self, $tails[0] ) - 1) {
    734 37         46 CORE::unshift @tails, splice( @{$self}, $index, 1 );
      37         129  
    735             }
    736              
    737 7         55 return @tails;
    738             }
    739              
    740             # Aliases
    741              
    742             *find = \&first;
    743             *remove = \&shift;
    744             *insert = \&push;
    745             *position = \&first_position;
    746              
    747             # Begin Private Methods
    748              
    749             sub _find {
    750 1033     1033   1381 my ($self,
    751             $match,
    752             $search) = @_;
    753 1033         1120 my $index = &{$search}( $self, $match );
      1033         1772  
    754 1033         1554 my $found = $self->[$index];
    755 908         2595 my $equal = defined $found ?
    756 1033 100       1708 &{$CMPSUBS{$self}}( $match, $found ) == 0 :
    757             '';
    758              
    759 1033         4335 return ( $found, $equal, $index );
    760             }
    761              
    762             sub _insert {
    763 349     349   537 my ($self,
    764             $item,
    765             $index) = @_;
    766 349         383 my $size = scalar @{$self};
      349         480  
    767              
    768 349 100       691 if ($index < $size / 2) {
    769 90         106 CORE::unshift @{$self}, splice( @{$self}, 0, $index, $item );
      90         133  
      90         396  
    770             }
    771             else {
    772 259         295 CORE::push @{$self}, splice( @{$self}, $index, $size - $index, $item );
      259         359  
      259         1075  
    773             }
    774             }
    775              
    776             sub _remove {
    777             # @_ == ($self, $match, $search)
    778 380     380   669 my ($found,
    779             $equal,
    780             $index) = _find( @_ );
    781              
    782 380 100       888 return $equal ? splice( @{$_[0]}, $index, 1 ) : undef;
      238         694  
    783             }
    784              
    785             sub _search_up {
    786 999     999   1377 my ($self,
    787             $match,
    788             $min) = @_;
    789 999         1912 my $cmpsub = $CMPSUBS{$self};
    790 999 50       2127 (defined $min) or
    791             $min = 0;
    792 999         1106 my $max = scalar @{$self};
      999         1417  
    793              
    794 999   100     2573 while ($min < $max and &{$cmpsub} ($match, $self->[$min]) > 0) {
      2088         4578  
    795 1506         7357 my $mid = $min + ($max - $min) / 2;
    796 1506 100       1831 if (&{$cmpsub} ($match, $self->[$mid]) > 0) {
      1506         2938  
    797 960         4673 $min = $mid + 1;
    798             }
    799             else {
    800 546         2613 $max = $mid;
    801             }
    802             }
    803              
    804 999         4399 return $min;
    805             }
    806              
    807             sub _search_down {
    808 1005     1005   1357 my ($self,
    809             $match,
    810             $max) = @_;
    811 1005         1884 my $cmpsub = $CMPSUBS{$self};
    812 753         1140 (defined $max) or
    813 1005 100       2605 $max = scalar @{$self} - 1;
    814 1005         1218 my $min = -1;
    815              
    816 1005   100     2580 while ($max > $min and &{$cmpsub} ($match, $self->[$max]) < 0) {
      2521         5460  
    817 1963         9456 my $mid = $max + ($min - $max) / 2;
    818 1963 100       2546 if (&{$cmpsub} ($match, $self->[$mid]) < 0) {
      1963         3953  
    819 1167         5713 $max = $mid - 1;
    820             }
    821             else {
    822 796         3810 $min = $mid;
    823             }
    824             }
    825              
    826 1005         4732 return $max;
    827             }
    828              
    829 0     0     sub _default_cmpsub { 0 };
    830              
    831             # End Private Methods
    832              
    833             =head1 ACKNOWLEDGMENTS
    834              
    835             This module's framework generated with L|Module::Starter>.
    836              
    837             =head1 AUTHOR
    838              
    839             S. Randall Sawyer, C<< >>
    840              
    841             =head1 BUGS
    842              
    843             Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.
    844              
    845             =head1 SUPPORT
    846              
    847             You can find documentation for this module with the perldoc command.
    848              
    849             perldoc Array::Ordered
    850              
    851             =head1 TODO
    852              
    853             Write an XS version so that 'order' works syntactically like 'tie'.
    854             Write a module for handling large sorted arrays using a balanced binary tree as a back-end.
    855              
    856             =head1 SEE ALSO
    857              
    858             L, L
    859              
    860             =head1 LICENSE AND COPYRIGHT
    861              
    862             Copyright 2013 S. Randall Sawyer. All rights reserved.
    863              
    864             This program is free software; you can redistribute it and/or modify it
    865             under the terms of the the Artistic License (2.0). You may obtain a
    866             copy of the full license at:
    867              
    868             L
    869              
    870             =cut
    871              
    872             1;
    873              
    874             __END__